Skip to content

泛型

约 730 字大约 2 分钟

2025-06-29

工作原理

类型擦除

Java 中的泛型是「伪泛型」,其实现依靠 Java 中所有类的父类——Object 泛型的声明与使用形如

public class ArrayList<T> {}
ArratList<Integer> list = new ArrayList<>();

这是一个指定了只能存放整型的集合,对于list添加其他类型的元素会触发编译时错误 但如果运行以下代码

ArratList<Integer> list1 = new ArrayList<>();
ArratList<String> list2 = new ArrayList<>();
System.out.print(list1.getClass() == list2.getClass());

输出结果为 true

这是因为泛型只依靠编译时进行检查,而在运行时所有的泛型都被擦除为Object类型

上下界

//Lev 1  
class Food{}

//Lev 2  
class Fruit extends Food{}  
class Meat extends Food{}

//Lev 3  
class Apple extends Fruit{}  
class Banana extends Fruit{}  
class Pork extends Meat{}  
class Beef extends Meat{}

//Lev 4  
class RedApple extends Apple{}  
class GreenApple extends Apple{}

上界 (Extends)

该泛型只接受继承(Extends)了某个类的元素,即只能存储上界元素的子类 如:Plate<? extends Fruit>image 对于上界,编译器只知道其中存放的类型为 Fruit 的子类,但究竟是 Apple, Banana 还是红绿苹果,不知道,子类(已有的泛型)对象不能指向父类(将要添加的对象)的引用,因此不可向其中添加数据 同样的,父类对象指向子类的引用,可以当作Fruit类读取

List<Integer> l1 = new ArrayList<>();  
l1.add(1);  
l1.add(2);  
l1.add(3);  
List<? extends Number> l2;  

l2 = l1; // 能够传入
l2.add(1); // 不能向内添加值,因为编译器不知道 l2 中的泛型整型还是浮点型还是其他,编译时异常
l2.add(null); // 任何类型都能为 null,可以理解为所有类的子类(?)
Number i = l2.get(0); // 可以以上界类型读取其中的值,因为其中能存储的值肯定为上界或上界的子类
Integer i = l2.get(0); // 编译器无法保证 l2 中的类型是否为 Integer,编译时异常

下界(Super)

与上界相反,该泛型只接受某个类型的基类的类 如:Plate<? super Fruit>image 对于下界,编译器知道其中的泛型至少为 Fruit 的超类(父类),父类(已有的泛型)对象指向子类(添加的对象)引用,因此能够向内添加数据 但因为不知道其上界,子类(获取出的对象)对象无法指向父类(已有的泛型)的引用,故不能获取其中的数据

List<Integer> l1 = new ArrayList<>();  
l1.add(1);  
l1.add(2);  
l1.add(3);  
List<? super Integer> l2;

l2 = l1; // 能够传入
Number i1 = 1;
Integer i2 = 1;
l2.add(i1); // Integer(子类)无法指向 Number(父类) 的引用,编译时异常
l2.add(i2); // Number 
Number i = l2.get(0); // l2 中的类型为 Integer 或 Integer 的父类,编译器不能判断 Number 一定不为 l2 中的子类,编译时异常
Object i4 = l2.get(0); // Object 为所有类的父类,它是个例外