类型擦除
Java编译器通过类型擦除(type erasure)对泛型提供支持:
1. 将所有类型参数替换为他们的界(bound),若类型参数是不受限的(unbounded),则替换为Object,因此产生的字节码中只含有普通类、接口、方法
例如有如下的Node类:
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 Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
2. 插入强制类型转换来保证类型安全。
例如下面的方法:
public String myMethod(Integer x) {
List<String> stringList = new LinkedList<String>();
List list = stringList;
list.add(x);
return stringList.iterator().next();
}
在类型擦除后变为:
public String myMethod(Integer x) {
List stringList = new LinkedList;
List list = stringList;
list.add(x);
return(String) stringList.iterator().next(); // 报运行时错误
}
3. 生成桥接方法(bridge method)来保证多态性。
比如当一个类实现了一个参数化的接口或是继承了一个参数化的类时。比如IntNode类继承了上面的Node类:
public class IntNode extends Node<Integer> {
public IntNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("IntNode.setData");
super.setData(data);
}
}
经过类型擦除后变为:
public class IntNode extends Node {
public IntNode (Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("IntNode.setData");
super.setData(data);
}
}
父类Node的setData(T data)
方法在类型擦除后变为setData(Object data)
,子类IntNode方法中的setData(Integer data)
方法就无法覆盖父类中的setData(Object data)
方法,因为参数不同,不是同一个方法。
为使泛型类型在类型擦除后仍具有多态性,编译器会在IntNode中生成一个桥接方法:
public void setData(Object data) {
setData((Integer) data);
}
4. 类型擦除保证了参数化类型不会产生新的类,因此泛型不会产生运行时系统开销。
原始类型
参数化类型经过类型擦除后产生的类就是原始类型(raw type)。
可以创建一个原始类型对象:
ArrayList rawArrayList = new ArrayList();
可以将参数化类型赋值给其原始类型:
ArrayList<Integer> intArrayList = new ArrayList<>();
ArrayList rawArrayList = intArrayList;
但是将原始类型赋值给参数化类型,会有未检查警告:
ArrayList rawArrayList = new ArrayList();
ArrayList<Integer> intArrayList = rawArrayList; // Unchecked assignment: 'java.util.ArrayList' to 'java.util.ArrayList<java.lang.Integer>'
通过原始类型调用泛型方法,也会有未检查警告
ArrayList rawArrayList = new ArrayList();
rawArrayList.add(8); // warning: Unchecked call to 'add(E)' as a member of raw type 'java.util.ArrayList'
警告表示原始类型绕过了泛型类型检查,意味着编译器没有足够的类型信息来进行全部的类型检查以保证类型安全
类型推断让我们省略构造器中的类型参数
ArrayList<Integer> intArrayList = new ArrayList<Integer>(); // Explicit type argument Integer can be replaced with <>
ArrayList<Integer> intArrayList = new ArrayList<>();
类型推断可以让我们像调用普通方法一样调用泛型方法
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2); // Explicit type arguments can be inferred
boolean same = Util. compare(p1, p2);
参考
https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.10
https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html
https://docs.oracle.com/javase/tutorial/extra/generics/legacy.html