这个问题与类型擦除及其对数组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。我对此没有问题。但是,在理解E
和toArray3
的类型参数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[]
。
答案 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
时。如果您实际发布了堆栈跟踪,我们可能会验证这一点。