数组return可用于赋值,不可用于循环

Array return can be used in assignment, but not in loop

我在发布 different question 的答案时遇到了一个小谜团。 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;
    }
}

在 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[]都有长度变量。为什么在循环声明中使用访问器方法会导致异常?

存在 ClassCastException 的原因——无法从 Object[] 转换为 String[]——是因为编译器在使用泛型时所做的事情。当调用 returnItems() 时,编译器插入一个转换为 String[],因为 returnItems returns T[]。编译时的类型擦除意味着它是 returning 一个 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);
}

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

创建 animals
Playground<String> animals = new Playground<String>(5, String.class);

更新

以下是关于为什么第一个示例有效(分配给 Object[])而第二个示例无效(直接在 [= 上访问字段 length)的合理解释95=] returnItems() 方法的类型。

第一个例子

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

JLS, Section 5.2 描述了“赋值上下文”,它管理将表达式的值赋给变量时发生的情况。

The only exceptions that may arise from conversions in an assignment context are:

  • A ClassCastException if, after the conversions above have been applied, the resulting value is an object which is not an instance of a subclass or subinterface of the erasure (§4.6) of the type of the variable.

This circumstance can only arise as a result of heap pollution (§4.12.2). In practice, implementations need only perform casts when accessing a field or method of an object of parameterized type when the erased type of the field, or the erased return type of the method, differ from its unerased type.

...

编译器不需要在此处向 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]he identifier names a single accessible member field in type T, and the type of the field access expression is the type of the member field after capture conversion (§5.1.10).

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

因为 arrayOfItems 的类型确实是 Object[],转换失败。

如上所述,使用 Array.newInstance 创建通用数组解决了这个问题,因为正在创建一个实际的 String[]。通过该更改,插入的演员表仍然存在,但这次成功了。