java遗珠之泛型类型擦除

Posted 吴冬冬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java遗珠之泛型类型擦除相关的知识,希望对你有一定的参考价值。

擦除规则

泛型的作用之前已经介绍过了只是用于编译之前更为严格的类型检查,其他的一些特性也都是编译之前的,在编译之后泛型是会被擦除掉的。

类型擦除所做的事情如下:

  1. 如果是无界限的则会把类型参数替换成Object,如果是有界限的则会把类型参数替换为界限类型。
  2. 插入一些类型转换来保证类型安全
  3. 为了保证从泛型类型继承的多态性会增加一些桥接方法。

泛型类型的擦除

无边界类型擦除

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才对。

但是代码肯定是经过擦除的,从这个角度来说

  1. setData("Hello")调用的参数其实Object,是父类的方法,因为子类的参数是Integer。
  2. 然后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.

以上是关于java遗珠之泛型类型擦除的主要内容,如果未能解决你的问题,请参考以下文章

java遗珠之泛型不可靠类型

java遗珠之泛型不可靠类型

java遗珠之泛型七大限制

java遗珠之泛型七大限制

java遗珠之泛型的作用

java遗珠之泛型类型推断