搜索了很久,大概看懂了一点,以下只是个人见解,如果有错,还请指出。
1、泛型擦除
首先,java虚拟机是没有泛型的,会进行所谓的泛型擦除。
虽然 Java 的泛型在运行时会被擦除,但它们在编译时提供了类型安全性、代码重用性、可读性和减少强制类型转换等好处。因此,尽管 JVM 不保留泛型信息,泛型在 Java 编程中的作用仍然非常重要。
泛型擦除会存在一些问题,比如下边这个代码
`
public class Cmower {
public static void method(Arraylist<String> list) {
System.out.println("Arraylist<String> list");
}
public static void method(Arraylist<Date> list) {
System.out.println("Arraylist<Date> list");
}
}`
这在编译时就会报错
在浅层的意识上,我们会想当然地认为 Arraylist<String> list 和 Arraylist<Date> list 是两种不同的类型,因为 String 和 Date 是不同的类。
但由于类型擦除的原因,以上代码是不会通过编译的——编译器会提示一个错误(这正是类型擦除引发的那些“问题”): 大致的意思就是,这两个方法的参数类型在擦除后是相同的。
也就是说,method(Arraylist<String> list) 和 method(Arraylist<Date> list) 是同一种参数类型的方法,不能同时存在。类型变量 String 和 Date 在擦除后会自动消失,method 方法的实际参数是 Arraylist list。
2、泛型通配符和泛型限定符
通配符使用英文的问号(?)来表示。在我们创建一个泛型对象时,可以使用关键字 extends 限定子类,也可以使用关键字 super 限定父类。
需要注意的是:
(1) ? extends T —— 上界通配符
- 表示类型是
T或T的子类。 - 只能读取,不能写入(除了
null)。
作用:
- 适用于当你只想从集合中读取数据,而不想往集合里添加数据的场景。
- 你可以确定从集合中读取的元素至少是
T类型或其子类。
限制:
- 不能往集合中添加任何元素,除了
null。因为编译器只知道这个集合可能包含某个类型T或T的子类,但无法确定具体的子类是什么。例如,假设list是ArrayList<? extends Animal>,它可能是ArrayList<Dog>,也可能是ArrayList<Cat>,所以你不能安全地向其中添加一个Dog或Cat对象。
即使是这样子的:
ArrayList<? extends Animal> list = new ArrayList<Dog>();
Animal a = list.get(0); // 可以获取元素,返回类型是 Animal 或其子类
list.add(new Dog()); // 编译错误:不能添加元素
编译看左边,编译器只知道list中存储的应该是Animal或其一个子类,不能确定是哪一个子类,所以不能够往list中添加元素,但是可以取元素,多态的缘故,取元素可以同意强制为Anamial类型。
(2). ? super T —— 下界通配符
- 表示类型是
T或T的超类。 - 可以写入,读取有限制(读取只能为
Object类型)。
作用:
- 适用于你需要往集合中添加数据的场景。
- 可以安全地添加
T及其子类的对象,因为通配符保证集合至少可以接受T类型的对象。
限制:
- 读取时有类型限制。你无法确定集合中存放的元素具体是什么类型,只知道它是
T的某个超类,可能是Object,也可能是其他父类。所以你不能将读取的对象赋值给具体的T类型,只能赋值给Object。
ArrayList<? super Dog> list = new ArrayList<Animal>();
list.add(new Dog()); // 可以添加 Dog 类型及其子类
list.add(new Puppy()); // 可以添加 Puppy 类型(Dog 的子类)
Dog d = list.get(0); // 编译错误:无法确定返回的具体类型
Object o = list.get(0); // 只能作为 Object 类型读取
? super T代表list中存放的类型是super或者其父类中的任何一个类型,所以当我们往其中add Dog及其子类时不会报错,因为编译器知道Dog和其子类都满足条件,符合多态,但是我们不能够get对象,因为只知道是Dog或者其任何一个父类,不能确定是哪一个父类,所以get时不能精确的Get,只能转换为一个Object对象。