121. Java 泛型 - 泛型类型

121. Java 泛型 - 泛型类型

1. 一个简单的 Box

非泛型 Box

让我们先看一个简单的非泛型 Box 类,它使用了 Object 类型来存储任何类型的数据:

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

在这个例子中,Box 类中的 objectObject 类型,可以存储任何类型的对象。然而,使用 Object 类型也带来了问题:虽然我们可以存储任何类型的对象,但在获取对象时,必须进行强制类型转换。这可能导致运行时错误。例如,如果你将一个 Integer 存入 Box 中,之后却尝试从 Box 中取出一个 String,将会抛出 ClassCastException

泛型版本的 Box

为了避免这种类型转换的麻烦,Java 提供了泛型来确保类型安全。通过将 Box 类改为泛型版本,我们可以确保 Box 只能存储指定类型的对象,消除了类型转换的风险。

泛型类使用以下格式来定义:

class name<T1, T2, ..., Tn> { /* ... */ }

类型参数(T1T2Tn)在类名后面用尖括号 <> 括起来。它们表示类型变量,你可以在类中任何需要的地方使用这些类型变量。

下面是改进后的泛型版本 Box 类:

/**
 * 泛型版的 Box 类。
 * @param <T> 表示盒子中存储的值的类型
 */
public class Box<T> {
    // T 代表 "Type",即类型
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

在这个版本中,T 是一个类型变量,它在整个 Box 类中代表某种具体类型。你可以使用任何非基本类型(类类型、接口类型等)来替代 T

类型参数命名约定

在 Java 中,类型参数的命名有一些约定:

  • E 代表元素(广泛用于 Java Collections Framework
  • K 代表键(Key
  • N 代表数字(Number
  • T 代表类型(Type
  • V 代表值(Value
  • S、U、V 等代表第二、第三、第四种类型等

这些命名约定帮助开发者在阅读代码时能快速识别类型参数,避免与普通变量名混淆。

调用和实例化泛型类型

要使用泛型类 Box,需要指定具体的类型。例如,使用 Box<Integer> 来表示一个只能存储 Integer 类型的 Box

Box<Integer> integerBox;

这个语句表示 integerBox 变量将引用一个 Box 类型的对象,该对象只能存储 Integer 类型的值。记住,这只是声明,并没有创建对象。要创建对象,我们可以像平常一样使用 new 关键字,同时在类型参数中指定具体类型:

Box<Integer> integerBox = new Box<Integer>();

Java SE 7 引入的菱形语法(Diamond)

Java SE 7 开始,Java 编译器能够推断出泛型类型的具体类型,因此你可以省略类构造函数中的类型参数。我们称这种语法为“菱形语法”。

Box<Integer> integerBox = new Box<>();  // 编译器自动推断出类型为 Integer

多个类型参数

可以为泛型类定义多个类型参数。例如,我们有一个 Pair 接口和实现它的 OrderedPair 类:

public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {
    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}

这个 OrderedPair 类接受两个类型参数:K(键)和 V(值)。你可以创建多个实例,分别使用不同的类型参数:

Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String> p2 = new OrderedPair<>("hello", "world");

这里,p1 是一个包含 StringInteger 类型的键值对,而 p2 是一个包含两个 String 类型的键值对。你可以通过指定不同的类型参数来实例化 OrderedPair

使用菱形语法实例化

就像在单个类型参数的情况下,Java 编译器可以从上下文中推断出多个类型参数,因此我们可以使用菱形语法来简化代码:

OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");

Java 编译器会根据 OrderedPair<String, Integer> 自动推断出 KV 的类型。

参数化类型

泛型还允许你将类型参数作为另一个类型的参数。例如,OrderedPair<K, V> 中的 V 可以是一个泛型类型,如 Box<Integer>

OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<>());

这表示 p 是一个包含 StringBox<Integer> 类型的键值对。这样可以将复杂的类型组合起来,使得代码更加灵活和可重用。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容