类型擦除
类型擦除简介
Java 中的泛型是伪泛型。
类型擦除就是Java泛型只能用于在编译期间的静态类型检查,然后编译器生成的代码会擦除相应的类型信息,
这样到了运行期间实际上JVM根本不知道泛型所代表的具体类型。这样做的目的是因为Java泛型是1.5之后才被引入的,为了保持向下兼容。
对于这一点,如果阅读 Java 集合框架的源码,可以发现有些类其实并不支持泛型。
1 2 3 4 5 6 7 8 9
| public class Node<T> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } }
|
编译器做完相应的类型检查之后,实际上到了运行期间上面这段代码实际上将转换成:
1 2 3 4 5 6 7 8 9
| public class Node { private Object data; private Node next; public Node(Object data, Node next) { this.data = data; this.next = next; } public Object getData() { return data; } }
|
这意味着不管我们声明 Node<String> 还是Node<Integer>,到了运行期间,JVM 统统视为Node<Object>。
解决:
1 2 3 4 5 6 7 8 9 10
| public class Node<T extends Comparable<T>> { private T data; private Node<T> next; public Node(T data, Node<T> next) { this.data = data; this.next = next; } public T getData() { return data; } }
|
这样编译器就会将 T 出现的地方替换成 Comparable 而不再是默认的 Object 了:
1 2 3 4 5 6 7 8 9 10
| public class Node { private Comparable data; private Node next; public Node(Comparable data, Node next) { this.data = data; this.next = next; } public Comparable getData() { return data; } }
|
类型擦除带来的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class Problem1 { public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } }
|
- 对于泛型代码,Java 编译器实际上还会偷偷帮我们实现一个 Bridge Method。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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); } }
|
看完上面的分析之后,你可能会认为在类型擦除后,编译器会将Node和MyNode变成下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; } }
public class MyNode extends Node { public MyNode(Integer data) { super(data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } }
|
实际上 Java 编译器对上面代码自动还做了一个处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class MyNode extends Node { public void setData(Object data) { setData((Integer) data); } public void setData(Integer data) { System.out.println("MyNode.setData"); super.setData(data); } }
|
- Java 泛型很大程度上只能提供静态类型检查,然后类型的信息就会被擦除,所以利用类型参数创建实例的做法编译器不会通过。
1 2 3 4
| public static <E> void append(List<E> list) { E elem = new E(); list.add(elem); }
|
使用反射解决:
1 2 3 4 5
| public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); list.add(elem); }
|
1 2 3 4 5
| public static <E> void rtti(List<E> list) { if (list instanceof ArrayList<Integer>) { } }
|
ArrayList<Integer>, ArrayList<String>, LinkedList<Character>, … 和上面一样,有这个问题。
使用通配符解决:
1 2 3 4 5
| public static void rtti(List<?> list) { if (list instanceof ArrayList<?>) { } }
|