在 Java 中,泛型是一种强类型约束机制,可以在编译期间检查类型安全性,并且可以提高代码的复用性和可读性。
一个泛型类就是具有一个或多个类型变量的类。Arraylist 类引入的类型变量为 E(Element,元素的首字母),使用尖括号 <>
括起来,放在类名的后面。
然后,我们可以用具体的类型(比如字符串)替换类型变量来实例化泛型类。
Arraylist<String> list = new Arraylist<String>();
list. Add("aaa");
String str = list. Get(0);animals. Get
一、类型参数化
泛型的本质是参数化类型,也就是说,在定义类、接口或方法时,可以使用一个或多个类型参数来表示参数化类型。
例如这样可以定义一个泛型类。
public class Box<T> {
private T value;
public Box(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
在这个例子中,<T>
表示类型参数,可以在类中任何需要使用类型的地方使用 T 代替具体的类型。通过使用泛型,我们可以创建一个可以存储任何类型对象的盒子。
Box<Integer> intBox = new Box<>(123);
Box<String> strBox = new Box<>("Hello, world!");
泛型在实际开发中的应用非常广泛,例如集合框架中的 List、Set、Map 等容器类,以及并发框架中的 Future、Callable 等工具类都使用了泛型。
二、类型擦除
在 Java 的泛型机制中,有两个重要的概念:类型擦除和通配符。
泛型在编译时会将泛型类型擦除,将泛型类型替换成 Object 类型。这是为了向后兼容,避免对原有的 Java 代码造成影响。
例如,对于下面的代码:
List<Integer> intList = new ArrayList<>();
intList.add(123);
int value = intList.get(0);
在编译时,Java 编译器会将泛型类型 List<Integer>
替换成 List<Object>
,将 get 方法的返回值类型 Integer 替换成 Object,生成的字节码与下面的代码等价:
List intList = new ArrayList();
intList.add(Integer.valueOf(123));
int value = (Integer) intList.get(0);
Java 泛型只在编译时起作用,运行时并不会保留泛型类型信息。
三、通配符
通配符用于表示某种未知的类型,例如 List<?>
表示一个可以存储任何类型对象的 List,但是不能对其中的元素进行添加操作。通配符可以用来解决类型不确定的情况,例如在方法参数或返回值中使用。
使用通配符可以使方法更加通用,同时保证类型安全。
例如,定义一个泛型方法:
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.print(obj + " ");
}
System.out.println();
}
这个方法可以接受任意类型的 List,例如 List<Integer>
、List<String>
等等。
(1)上限通配符
泛型还提供了上限通配符 <? extends T>
,表示通配符只能接受 T 或 T 的子类。使用上限通配符可以提高程序的类型安全性。
例如,定义一个方法,只接受 Number 及其子类的 List:
public static void printNumberList(List<? extends Number> list) {
for (Number num : list) {
System.out.print(num + " ");
}
System.out.println();
}
这个方法可以接受 List<Integer>
、List<Double>
等等。
(2)下限通配符
下限通配符(Lower Bounded Wildcards)用 super 关键字来声明,其语法形式为 <? super T>
,其中 T 表示类型参数。它表示的是该类型参数必须是某个指定类的超类(包括该类本身)。
当我们需要往一个泛型集合中添加元素时,如果使用的是上限通配符,集合中的元素类型可能会被限制,从而无法添加某些类型的元素。但是,如果我们使用下限通配符,可以将指定类型的子类型添加到集合中,保证了元素的完整性。
举个例子,假设有一个类 Animal,以及两个子类 Dog 和 Cat。现在我们有一个 List<? super Dog>
集合,它的类型参数必须是 Dog 或其父类类型。我们可以向该集合中添加 Dog 类型的元素,也可以添加它的子类。但是,不能向其中添加 Cat 类型的元素,因为 Cat 不是 Dog 的子类。
下面是一个使用下限通配符的示例:
List<? super Dog> animals = new ArrayList<>();
// 可以添加 Dog 类型的元素和其子类型元素
animals.add(new Dog());
animals.add(new Bulldog());
// 不能添加 Cat 类型的元素
animals.add(new Cat()); // 编译报错
需要注意的是,虽然使用下限通配符可以添加某些子类型元素,但是在读取元素时,我们只能确保其是 Object 类型的,无法确保其是指定类型或其父类型。因此,在读取元素时需要进行类型转换,如下所示:
List<? super Dog> animals = new ArrayList<>();
animals.add(new Dog());
// 读取元素时需要进行类型转换
Object animal = animals.get(0);
Dog dog = (Dog) animal;
总的来说,Java 的泛型机制是一种非常强大的类型约束机制,可以在编译时检查类型安全性,并提高代码的复用性和可读性。但是,在使用泛型时也需要注意类型擦除和通配符等问题,以确保代码的正确性。