class A { ... }
interface B { ... }
interface C { ... }
// 正确写法
class D <T extands A & B & C> { ... }
// 错误写法,无法通过编译
class D <T extands B & A & C> { ... }
类型擦除后:
public static void draw(Shape shape) { /*...*/ }
类型擦除和桥方法
在类型擦除时,编译器有时会创建一个被称为桥接方法的合成方法,作为类型擦除的一部分。
给予以下两个类:
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
考虑以下代码:
MyNode mn = new MyNode(5);
Node n = mn; // ⚠️编译器抛出一个未检查警告
n.setData("Hello");
Integer x = mn.data; // ❗️运行时抛出类型转换异常(ClassCastException)
类型擦除后的代码:
MyNode mn = new MyNode(5);
Node n = (MyNode) mn;
n.setData("Hello");
Integer x = (String) mn.data;
public class ArrayBuilder {
public static <T> void addToList (List<T> listArg, T... elements) {
for (T x : elements) {
listArg.add(x);
}
}
public static void faultyMethod(List<String>... l) {
Object[] objectArray = l; // Valid
objectArray[0] = Arrays.asList(42);
String s = l[0].get(0); // ClassCastException thrown here
}
}
HeapPollutionExample:
public class HeapPollutionExample {
public static void main(String[] args) {
List<String> stringListA = new ArrayList<String>();
List<String> stringListB = new ArrayList<String>();
ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
ArrayBuilder.addToList(stringListB, "Ten", "Eleven", "Twelve");
List<List<String>> listOfStringLists = new ArrayList<List<String>>();
ArrayBuilder.addToList(listOfStringLists,stringListA, stringListB);
ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
}
}
ArrayBuilder.addToList 方法定义产生以下警告:
warning: [varargs] Possible heap pollution from parameterized vararg type T
当编译器遇到 varargs 方法时,它会将 varargs 形式参数转为数组。但是, Java 不允许创建参数化类型的数组。
在该方法中,编译器将 T ... elements 转换为 T[] elements,经过类型擦除,编译器将参数转化为Object[] elements。因此可能存在堆污染。
// 错误方法
public static<E> void append(List<E> list) {
E elem = new E();// 编译报错
list.add(elem);
}
// 正确使用方法
public static<E> void append(List<E> list, Class<E> clazz) throws Exception {
E elem = clazz.newInstance();
list.add(elem);
}
// 调用
List<String> ls = new ArrayList<>();
append(ls, String.class);
无法声明类型为类型参数的静态字段
参考如果存在以下声明:
public class MobileDevice<T> {
public static T os;
//...
}
那么当允许类型参数的静态字段时,以下代码将混淆:
MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();
由于字段 os 是共享在 MobileDevice 类的对象之间的,所以 os 的实际类型到底是什么?
无法使用具有参数化类型的强制转换或 instanceof
instanceof
由于泛型擦除的原因,所以无法验证运行时使用泛型类型的参数化类型:
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<Integer>) {// 编译错误
// ...
}
}
运行时不跟踪类型参数,因此无法区分ArrayList<Integer>和ArrayList<String>。最多可以使用无界匹配符来验证 list 是否是 ArrayList 类型。
public static <E> void rtti(List<E> list) {
if (list instanceof ArrayList<?>) {// instanceof 需要一个可再生的类型
// ...
}
}
强制转换
通常,除非通过无界通配符对其进行参数化,否则无法强制转换为参数化类型。eg:
List<Integer> li = new ArrayList<>();
List<Number> ln = (List<Number>) li;// 编译时错误
Java 泛型学习笔记
内容均出自:Java泛型教程
泛型的关系
上述方法接受一个类型为
Box<Number>
的参数,但是并不能传递Box<Integer>
或Box<Double>
虽然我们已知Integer
是Number
子类,但是Box<Integer>
不是Box<Number>
的子类型。通配符
Class
Class
类型或其子类(<=)Class
Class
类型或其父类(>=)注意:
子类型
我们如下定义一个接口:PayloadList
下面这些 PayloadList 都是
List<String>
的子类型PayloadList<String, String>
PayloadList<String, Integer>
PayloadList<String, Exception>
泛型擦除
擦除通用类型
无界类型参数
源码:
由于类型参数
T
无界,因此编译器使用Object
替换:类型擦除后:
有界类型参数
源码:
类型参数有界时,编译器使用第一个边界类:
Comparable
替换类型参数:类型擦除后:
擦除通用方法
无界
T
是无界的,因此使用Object
替换它:类型擦除后:
有界
public static <T extends Shape> void draw(T shape) { /*...*/ }
T
是有界的,因此采用第一个边界类型:Shape
替换它。类型擦除后:
public static void draw(Shape shape) { /*...*/ }
类型擦除和桥方法
在类型擦除时,编译器有时会创建一个被称为桥接方法的合成方法,作为类型擦除的一部分。
给予以下两个类:
考虑以下代码:
类型擦除后的代码:
教程解释(与实际情况有出入)
代码解释:
Node<Integer>
)。实际情况
关于代码:
分析: 我们先通过命令
javap -c Class.class
查看编译后的 .class 文件:通过注解我们可以看出,反编译后的调用代码与之前代码行数大致对应关系:
我们可以看出,实际 MyNode 中确实生成了桥方法(setData(Object)),但是生成的代码中:
2: checkcast #6 // class java/lang/Integer
校验了 data 类型是否是 Integer,因此才在n.setData("Hello")
执行时抛出了异常。不可再生类型
可再生类型示例:
class Myclass<OtherClass> { /*...*/ }
,调用MyClass obj = new MyClass<OtherClass>
,obj 即为原始类型。堆污染
当参数化类型的变量引用不是该参数类型化的对象时,会发生堆污染。例如,在混合原始类型和参数化类型时,或者在执行未经检查的强制转换时,会发生堆污染。
示例
ArrayBuilder:
HeapPollutionExample:
ArrayBuilder.addToList 方法定义产生以下警告:
warning: [varargs] Possible heap pollution from parameterized vararg type T
当编译器遇到 varargs 方法时,它会将 varargs 形式参数转为数组。但是, Java 不允许创建参数化类型的数组。 在该方法中,编译器将T ... elements
转换为T[] elements
,经过类型擦除,编译器将参数转化为Object[] elements
。因此可能存在堆污染。ArrayBuilder.faultyMethod 方法中:
Object[] objectArray = l;
上述语句可能会引入堆污染。原本类型参数List<String>... l
,经过类型擦除后得到 List[] l,是 Object[] 的子类型。 因此语句objectArray[0] = Arrays.asList(42);
不会有警告或错误。 而在语句String s = l[0].get(0);
中,将原本为 Integer 类型的对象转换为 String 时失败,抛出 ClassCastException 异常。泛型的限制
无法使用基元类型参数实例化通用类型
无法创建如:
List<int> list = new ArrayList<>();
只能使用基本类型的装箱类型,例如:List<Integer> list = new ArrayList<>();
无法创建类型参数的实例
无法声明类型为类型参数的静态字段
参考如果存在以下声明:
那么当允许类型参数的静态字段时,以下代码将混淆:
由于字段 os 是共享在 MobileDevice 类的对象之间的,所以 os 的实际类型到底是什么?
无法使用具有参数化类型的强制转换或 instanceof
instanceof
由于泛型擦除的原因,所以无法验证运行时使用泛型类型的参数化类型:
运行时不跟踪类型参数,因此无法区分
ArrayList<Integer>
和ArrayList<String>
。最多可以使用无界匹配符来验证 list 是否是 ArrayList 类型。强制转换
通常,除非通过无界通配符对其进行参数化,否则无法强制转换为参数化类型。eg:
但是某些情况下,编译器知道类型参数始终有效并允许强制转换。eg:
无法创建参数化类型的数组
如下代码无法编译:
参考一下代码:
因为泛型被擦除,我们无法区分
ArrayList<String>
和ArrayList<Integer>
,因此,允许参数化列表数组时,上述代码无法抛出我们需要的异常。无法创建、捕获或抛出参数化类型的对象
泛型类不能直接或者间接扩展 Throwable 类。eg:
方法无法捕获类型参数的实例:
但是可以在 throws 子句中使用类型参数:
无法重载每个重载形式参数类型擦除到相同原始类型的方法
一个类不能有两个在类型擦除后具有相同签名的重载方法。