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
类中的 object
是 Object
类型,可以存储任何类型的对象。然而,使用 Object
类型也带来了问题:虽然我们可以存储任何类型的对象,但在获取对象时,必须进行强制类型转换。这可能导致运行时错误。例如,如果你将一个 Integer
存入 Box
中,之后却尝试从 Box
中取出一个 String
,将会抛出 ClassCastException
。
泛型版本的 Box
类
为了避免这种类型转换的麻烦,Java
提供了泛型来确保类型安全。通过将 Box
类改为泛型版本,我们可以确保 Box
只能存储指定类型的对象,消除了类型转换的风险。
泛型类使用以下格式来定义:
class name<T1, T2, ..., Tn> { /* ... */ }
类型参数(T1
、T2
、Tn
)在类名后面用尖括号 <>
括起来。它们表示类型变量,你可以在类中任何需要的地方使用这些类型变量。
下面是改进后的泛型版本 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
是一个包含 String
和 Integer
类型的键值对,而 p2
是一个包含两个 String
类型的键值对。你可以通过指定不同的类型参数来实例化 OrderedPair
。
使用菱形语法实例化
就像在单个类型参数的情况下,Java 编译器可以从上下文中推断出多个类型参数,因此我们可以使用菱形语法来简化代码:
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
Java 编译器会根据 OrderedPair<String, Integer>
自动推断出 K
和 V
的类型。
参数化类型
泛型还允许你将类型参数作为另一个类型的参数。例如,OrderedPair<K, V>
中的 V
可以是一个泛型类型,如 Box<Integer>
:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<>());
这表示 p
是一个包含 String
和 Box<Integer>
类型的键值对。这样可以将复杂的类型组合起来,使得代码更加灵活和可重用。