Java:无法理解非静态方法的类型擦除

时间:2011-10-19 20:05:33

标签: java generics

这个问题与类型擦除及其对数组SCJP的影响有关(Mughal - 第7版,第726页)。以下是代码:

public class MyStack<E> implements IStack<E> {
    // Top of stack.
    private Node<E> tos;
    // Size of stack
    private int numOfElements;

    :

    // Incorrect version
    public E[] toArray3() {
        E[] toArray = (E[])new Object[numOfElements];
        int i=0;
        for (E data : this) {
           toArray[i++] = data;
        }
        return toArray;
    }

    // Correct version
    public E[] toArray4(E[] toArray) {
        if (toArray.length != numOfElements) {
            toArray = E[])java.lang.reflect.Array.newInstance(toArray.getClass().getComponentType(), numOfElements);
        }
        int i=0;
        for (E data : this) {
             toArray[i++] = data;
        }
        return toArray;
    }
}

根据该书,方法toArray3是不正确的版本,而方法toArray4是正确的版本,因为数组是reifiable type。我对此没有问题。但是,在理解EtoArray3的类型参数toArray4为何不同时,我遇到了问题。

这是我发现的方式。假设我有以下内容:

MyStack<Integer> intStack = new MyStack<Integer>();
intStack.push(9);
Integer[] x = intStack.toArray4(new Integer[0]); // clause 1
System.out.println(x.length);
Integer[] y = intStack.toArray4(new Object[0]); // clause 2  - compiler error
System.out.println(y.length);
Integer[] z = intStack.toArray3(); // clause 3
System.out.println(z.length);

一个。对于第1条,没问题。 toArray4中的类型参数E似乎是Integer。

湾对于第2条,错误是Integer[]不适用于Object[]。这是否意味着toArray4(E[] toArray)的类型删除是toArray4[Integer[] toArray)?为什么不对象[]? (虽然我知道intStack已经被Integer证实了。)

℃。对于第3节,运行时错误是指Object[]无法分配给Integer[]。我理解这一点,但为什么现在是E toArray3上的类型参数Object而不是Integer

我希望类型参数E ON toArray3与toArray4相同,因为intStack的类型参数E本身是整数类型,由toArray3和toArray4使用。

请注意,在toArray3的正文中,数组对象已被强制转换为E[]

2 个答案:

答案 0 :(得分:2)

a 上,这里毫无疑问。

b 上,在类型验证后输入。在编译时,所有类型都在这里。

c 上,您明确地创建了一个Object的数组,而不是Integer - 这里没有魔法。

type参数保持不变。只是你无法将Object[]投射到Integer[]

远离堆栈,缩小问题范围:

public class MyStack<E> {
    public static void main(String[] args) {
        Integer[] test = new MyStack<Integer>().test();
    }

    public E[] test() {
        return (E[]) (new Object[10]);
    }
}

给我们以下字节码:

Compiled from "MyStack.java"
public class org.acm.afilippov.stacko.MyStack extends java.lang.Object{
public org.acm.afilippov.stacko.MyStack();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new #2; //class org/acm/afilippov/stacko/MyStack
   3:   dup
   4:   invokespecial   #3; //Method "<init>":()V
   7:   invokevirtual   #4; //Method test:()[Ljava/lang/Object;
   10:  checkcast   #5; //class "[Ljava/lang/Integer;"
   13:  astore_1
   14:  return

public java.lang.Object[] test();
  Code:
   0:   bipush  10
   2:   anewarray   #6; //class java/lang/Object
   5:   checkcast   #7; //class "[Ljava/lang/Object;"
   8:   areturn

}

请注意,在test()方法中,通用类型会被删除 - 它是new Object[]checkcast Object[]

main中,您指定一个非泛型变量,表示类型转换;因此checkcast #5; //class "[Ljava/lang/Integer;"

b 中让您担心的诀窍是基于数组协变的事实,并且允许以下内容:

Object[] array = new Integer[10];

因此,如果 b ,通用方法的结果类型为Object[] - 但由于您可以将Integer[]分配给Object[],所以没关系。请参阅Generics FAQ

答案 1 :(得分:1)

这些方法之间的区别在于toArray4()使用反射来获取表示数组的正确组件类型的Class对象。这样可以确保在运行时已知类型信息,而不管类型擦除。

关键是这一行toArray4()

Array.newInstance(toArray.getClass().getComponentType(), numOfElements);

与通用对象不同,数组可以提供其组件类型,因为Integer[]之类的对象具有相应的Class对象,可以检查此信息。

因此,toArray4()实际上在Integer[]E时实例化Integer对象,而toArray3()实例化Object[]和调用代码(通过类型擦除)尝试将其强制转换为Integer[],正确导致ClassCastException

通常,反射通常用作键入擦除的变通方法。

作为一个注释,Java中的数组往往具有与之相关的某些“魔力”,特别是在反射方面。例如,如果您查看Class#getComponentType()Array.newInstance()的来源,则会导致本机代码。

编辑:我认为您混淆的根源是toArray3()中的演员阵容:

E[] toArray = (E[])new Object[numOfElements];

导致ClassCastException的广告。相反,它是由类型擦除添加的第二个演员。为了说明,让我们重写toArray3(),就好像已经应用了类型擦除一样:

public Object[] toArray3() {  
   Object[] toArray = new Object[numOfElements]; //first cast no longer necessary
   int i=0;  
   for (Object data : this) {  //pretend MyStack is no longer generic
      toArray[i++] = data;  
   }  
   return toArray;  
}

然后在调用代码中:

Integer[] z = (Integer[])intStack.toArray3(); //ClassCastException

正如您所看到的,ClassCastException不是在方法中抛出,而是在使用强制转换为您创建类型的强制转换将其结果分配给z时。如果您实际发布了堆栈跟踪,我们可能会验证这一点。