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

Ben*_* I. 6 java arrays generics class

当我遇到一个小小的谜时,我正在回答一个不同的问题.类定义(稍微修改自原始提问者)在这里:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

主要是,然后我们创建一个新的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]);
}
Run Code Online (Sandbox Code Playgroud)

但这会ClassCastException 在for循环声明中创建一个.

for(int i=0; i < animals.returnItems().length; i++) {
        System.out.println(animals.returnItems()[i]);
}
Run Code Online (Sandbox Code Playgroud)

两个Object[]S和String[]■找长度变量.为什么在循环声明中使用访问器方法会导致异常?

rge*_*man 7

有一个ClassCastException- 无法转换Object[]String[]- 的原因是编译器在使用泛型时所做的事情.调用时returnItems(),编译器会插入一个强制转换String[],因为returnItems返回T[].编译时键入擦除意味着它返回一个Object[],但由于TString这里,编译器插入一个强制转换String[].但是原始对象arrayOfItems不是a String[],它是a Object[],所以演员表失败了.

这应该在编译期间导致"未经检查的强制转换"警告,从而Object[]T[].

您需要做的是遵循如何在Java中创建通用数组中的建议在创建通用数组.

Class<T>在你的构造函数中接受a ,这样你就可以从一开始就调用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);
}
Run Code Online (Sandbox Code Playgroud)

然后你可以animals通过传递创建String.class:

Playground<String> animals = new Playground<String>(5, String.class);
Run Code Online (Sandbox Code Playgroud)

更新

以下是关于为什么第一个示例Object[]在第二个示例不起作用时工作(分配给a )的原因(length直接在方法的返回类型上访问字段)的合理解释returnItems().

第一个例子

Object[] s = animals.returnItems();
for(int i=0; i < s.length;i++) {
        System.out.println(s[i]);
}
Run Code Online (Sandbox Code Playgroud)

JLS,第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]);
}
Run Code Online (Sandbox Code Playgroud)

ClassCastException这里似乎不是依赖于for循环; 通过简单的长度打印将发生此错误:

System.out.println(animals.returnItems().length);
Run Code Online (Sandbox Code Playgroud)

这是一个字段访问表达式,由JLS第15.11.1节涵盖.

[T]标识符在类型T中命名单个可访问的成员字段,字段访问表达式的类型是捕获转换后的成员字段的类型(第5.1.10节).

捕获转换将类型捕获为String[].编译器必须将强制转换插入到String[]此处,原因与插入方法调用的强制转换相同 - 字段或方法可能仅存在于捕获的类型上.

因为类型arrayOfItems确实是Object[],演员表失败了.

如上所述,创建通用数组可以Array.newInstance解决此问题,因为String[]正在创建实际数据.通过该更改,插入的强制转换仍然存在,但这次成功.