java遗珠之泛型类型擦除

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lastsweetop/article/details/83025092

擦除规则

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

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

  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 类型擦除问题

12-19

关于Java泛型,编译之后类型擦除,但为什么会出现下面两种不同输出的代码,对Object[]转型为T[] 和Array.newInstance(type,size),type 是Class[T] 类型,转型为T[] 有什么区别?rn[code=java]rnpublic class GenericArray2 rn private Object[] array;rn public GenericArray2(int sz) rn array = new Object[sz];rn rn public void put(int index, T item) rn array[index] = item;rn rn @SuppressWarnings("unchecked")rn public T get(int index) return (T)array[index]; rn @SuppressWarnings("unchecked")rn public T[] rep() rn return (T[])array; // Warning: unchecked castrn rn public static void main(String[] args) rn GenericArray2 gai =rn new GenericArray2(10);rn for(int i = 0; i < 10; i ++)rn gai.put(i, i);rn for(int i = 0; i < 10; i ++)rn System.out.print(gai.get(i) + " ");rn System.out.println();rn try rn Integer[] ia = gai.rep();rn catch(Exception e) System.out.println(e); rn rn rn[/code]rn输出:rn0 1 2 3 4 5 6 7 8 9 rnjava.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;rn为什么一下转型没用?rn public T[] rep() rn return (T[])array; // Warning: unchecked castrn rnrn第二段代码:rn[code=java]rnimport java.lang.reflect.*;rnrnpublic class GenericArrayWithTypeToken rn private T[] array;rn @SuppressWarnings("unchecked")rn public GenericArrayWithTypeToken(Class type, int sz) rn array = (T[])Array.newInstance(type, sz);rn rn public void put(int index, T item) rn array[index] = item;rn rn public T get(int index) return array[index]; rn // Expose the underlying representation:rn public T[] rep() return array; rn public static void main(String[] args) rn GenericArrayWithTypeToken gai =rn new GenericArrayWithTypeToken(rn Integer.class, 10);rn // This now works:rn Integer[] ia = gai.rep();rn rn [/code]rnrn此处 array = (T[])Array.newInstance(type, sz);就是可行,请问为什么会出现这样的差异,明明都是类型擦除。 论坛

没有更多推荐了,返回首页