擦除规则
泛型的作用之前已经介绍过了只是用于编译之前更为严格的类型检查,其他的一些特性也都是编译之前的,在编译之后泛型是会被擦除掉的。
类型擦除所做的事情如下:
- 如果是无界限的则会把类型参数替换成
Object
,如果是有界限的则会把类型参数替换为界限类型。 - 插入一些类型转换来保证类型安全
- 为了保证从泛型类型继承的多态性会增加一些桥接方法。
泛型类型的擦除
无边界类型擦除
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; }
// ...
}
按照规则第一条T
是无界的,会用Object
来代替
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; }
// ...
}
有边界类型擦除
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; }
// ...
}
有界限的会替换成边界类型
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; }
// ...
}
泛型方法的类型擦除
无边界类型擦除
public static <T> int count(T[] anArray, T elem) {
int cnt = 0;
for (T e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
和泛型类型同理,擦除如下:
public static <T> int count(T[] anArray, T elem) {
int cnt = 0;
for (T e : anArray)
if (e.equals(elem))
++cnt;
return cnt;
}
有边界类型擦除
class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }
public class Util {
public static <T extends Shape> void draw(T shape) { /* ... */ }
}
类型擦除桥接方法
想想下面那个类的擦除
public class ANode<T> {
public T data;
public ANode(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
public T getData() { return data; }
// ...
}
会被擦除成
public class ANode {
private Object data;
public ANode(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
public Object getData() { return data; }
// ...
}
然后再看它的子类
public class MyNode extends ANode<Integer> {
public MyNode(Integer data) {
super(data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
擦除之后
public class MyNode extends ANode {
public MyNode(Integer data) {
super(data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
这就有意思了我们在main方法里调用如下:
public static void main(String[] args) {
ANode n = new MyNode(5);
n.setData("Hello");
Integer x = (Integer) n.getData(); // Causes a ClassCastException to be thrown.
}
如果只看擦除前的代码,setData("Hello")
调用的应该是子类的方法,参数不应该是String才对。
但是代码肯定是经过擦除的,从这个角度来说
setData("Hello")
调用的参数其实Object,是父类的方法,因为子类的参数是Integer。- 然后
getData()
返回的是Object类型,但指向的还是String类型,强转依然会报错。
嗯,到目前为止只是我们期望的,那么一运行,蒙了,直接在setData("Hello")
就报错了。
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at com.sweetop.studycore.generic.MyNode.main(MyNode.java:18)
这是因为为了保证泛型类型擦除后依然具有多态性,编译器在编译的时候自动增加了一个桥接方法,因此子类擦除后应该是这样的
public class MyNode extends ANode {
public MyNode(Integer data) {
super(data);
}
public void setData(Object data) {
setData((Integer) data);
}
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
如果是这样的话,那么子类就重写了父类的方法,调用的其实还是子类的setData(Object data)
.
因此就看到了上面那个错误。
之后碰到继承泛型类型的时候,一定要注意这种问题。最好在使用之前先做下类型判断
public static void main(String[] args) {
ANode n = new MyNode(5);
Object a = "1";
if (a instanceof Integer) {
n.setData("1");
}
Integer x = (Integer) n.getData(); // Causes a ClassCastException to be thrown.
}