一、什么是协变与反协变
图中从上到下的关系就被称为协变:父类型到子类型:越来越具体specific返回值类型不变或变得更具体;异常的类型也是如此。
父类型à子类型:越来越具体specific。参数类型:要相反的变化,要么不变或越来越抽象
二、泛型不是协变的
我们观察上面 的两个说法,ArrayList 是List的子类但是List 不是 List的子类。
这是因为类型参数的类型信息在代码编译完成后被编译器丢弃;因此该类型信息在运行时不可用。
这一过程叫做类型擦除。从这里开始我们进一步对泛型和类型擦除进行了解
三、深入探究
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
请问,上面代码最终结果输出的是什么?
上面的代码中涉及到了泛型,而输出的结果缘由是类型擦除。
类型擦除:如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或对象。因此,生成的字节码只包含普通的类、接口和方法。
不能将一个整形数组视为一个数字类型的数组
如上图中的操作在java中是不被允许的
下图总结了泛型不是协变的结构
进一步探究类型擦除结果
public class Erasure <T extends String>{
// public class Erasure <T>{
T object;
public Erasure(T object) {
this.object = object;
}
}
测试结果为
Field name object type:java.lang.String
由上面的代码和结果我们可以得到:
在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 则会被转译成普通的 Object 类型,如果指定了上限如 则类型参数就被替换成类型上限
四、通配符
除了用 表示泛型外,还有 <?>这种形式。? 被称为通配符。
已经有了 的形式了,为什么还要引进 <?>这样的概念呢?
在现实编码中,希望泛型能够处理某一范围内的数据类型,比如某个类和它的子类,对此 Java 引入了通配符这个概念。所以,通配符的出现是为了指定泛型中的类型范围。
通配符有 3 种形式。
- <?>被称作无限定的通配符。
- <? extends T>被称作有上限的通配符。
- <? super T>被称作有下限的通配符。
无限定通配符 <?>
无限定通配符经常与容器类配合使用,它其中的 ? 其实代表的是未知类型,所以涉及到 ? 时的操作,一定与具体类型无关
public <T> void test(Collection<T> collection){
collection.add((T)new Integer(12));
collection.add((T)"123");
}
public class Test2 <T,E extends T>{
T value1;
E value2;
}
E 类型是 T 类型的子类,显然这种情况类型参数更适合。