数组返回可用于赋值,但不能用于循环

时间:2015-01-16 17:13:42

标签: java arrays class generics

当我遇到一个小谜时,我正在发布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[]都有长度变量。为什么在循环声明中使用访问器方法会导致异常?

1 个答案:

答案 0 :(得分:7)

ClassCastException的原因 - 无法从Object[]强制转换为String[] - 是因为编译器在使用泛型时所做的事情。调用returnItems()时,编译器会将强制转换插入String[],因为returnItems会返回T[]。编译时键入擦除意味着它返回Object[],但由于此处TString,编译器会将转换插入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[]。通过该更改,插入的强制转换仍然存在,但这次成功。