当我遇到一个小谜时,我正在发布different question的答案。类定义(稍微从原始提问者修改)在这里:
public class Playground<T>{
private int pos;
private final int size;
private T[] arrayOfItems;
public Playground(int size){
this.size = size;
pos = 0;
arrayOfItems = (T[]) new Object[size];
}
public void addItem(T item) {
arrayOfItems[pos] = item;
pos++;
}
public void displayItems() {
for(int i = 0;i<pos;i++){
System.out.println(arrayOfItems[i]);
}
}
public T[] returnItems() {
return (T[]) arrayOfItems;
}
}
在main中,我们然后创建一个新的Playground Playground<String> animals = new Playground<String>(5);
并在其中添加一些动物字符串。 (狗,猫等)。
神秘之处在于它的作用:
Object[] s = animals.returnItems();
for(int i=0; i < s.length; i++) {
System.out.println(s[i]);
}
但这会在for循环声明中创建ClassCastException
。
for(int i=0; i < animals.returnItems().length; i++) {
System.out.println(animals.returnItems()[i]);
}
Object[]
和String[]
都有长度变量。为什么在循环声明中使用访问器方法会导致异常?
答案 0 :(得分:7)
有ClassCastException
的原因 - 无法从Object[]
强制转换为String[]
- 是因为编译器在使用泛型时所做的事情。调用returnItems()
时,编译器会将强制转换插入String[]
,因为returnItems
会返回T[]
。编译时键入擦除意味着它返回Object[]
,但由于此处T
为String
,编译器会将转换插入String[]
。但原始对象arrayOfItems
不是String[]
,而是Object[]
,因此投射失败。
这应该会在编译过程中产生“未经检查的强制转换”警告,从Object[]
到T[]
。
您需要做的是遵循How to create a generic array in Java?中有关创建通用数组的建议。
在构建器中接受Class<T>
,这样您就可以从一开始就致电Array.newInstance
并获得T[]
。
@SuppressWarnings("unchecked") // This suppression is safe.
public Playground(int size, Class<T> clazz){
this.size = size;
pos = 0;
arrayOfItems = (T[]) Array.newInstance(clazz, size);
}
然后,您可以通过传递animals
:
String.class
Playground<String> animals = new Playground<String>(5, String.class);
<强>更新强>
以下是关于为什么第一个示例在第二个示例不起作用时工作(分配给Object[]
)的合理解释(直接在返回类型上访问字段length
returnItems()
方法。
第一个例子
Object[] s = animals.returnItems();
for(int i=0; i < s.length;i++) {
System.out.println(s[i]);
}
JLS, Section 5.2描述了“分配上下文”,它控制将表达式中的值赋给变量时会发生什么。
分配上下文中的转换可能产生的唯一例外是:
- ClassCastException如果在应用了上述转换之后,结果值是一个对象,该对象不是变量类型的擦除(第4.6节)的子类或子接口的实例。
这种情况只能由于堆污染而产生(§4.12.2)。实际上,当字段的擦除类型或方法的擦除返回类型与其未擦除类型不同时,实现仅需要在访问参数化类型的对象的字段或方法时执行强制转换。
...
编译器不需要在此处向String[]
插入强制转换。稍后访问length
字段时,变量已经是Object[]
类型,因此这里没有问题。
第二个例子
for(int i=0; i < animals.returnItems().length;i++) {
System.out.println(animals.returnItems()[i]);
}
ClassCastException
此处似乎不依赖于for
循环;使用长度的简单打印将发生此错误:
System.out.println(animals.returnItems().length);
这是字段访问表达式,由JLS, Section 15.11.1涵盖。
[T]标识符在类型T中命名一个可访问的成员字段,字段访问表达式的类型是捕获转换后的成员字段的类型(第5.1.10节)。
捕获转换将类型捕获为String[]
。编译器必须在此处插入强制转换为String[]
,原因与插入方法调用的强制转换相同 - 字段或方法可能仅存在于捕获的类型上。
因为arrayOfItems
的类型确实是Object[]
,所以广告投放失败。
如上所述,使用Array.newInstance
创建通用数组可以解决此问题,因为正在创建实际的String[]
。通过该更改,插入的强制转换仍然存在,但这次成功。