首页 > 解决方案 > 在什么情况下强制转换 ArrayList.toArray() 到 (Type[]) 有可能出现“类转换错误”

问题描述

当突出显示“toArray”时,我的 IDE(IntelliJ)显示了这一点:

java.util.ArrayList

公共对象[] toArray()

这会返回一个Object[]; 据我所知,这意味着大多数用途需要铸造。但是我不确定它是否会在这种情况下工作(此方法是带有参数的泛型类的一部分<Type>):

private Type evaluate(int operator_index){
        ArrayList<Type> this_args = new ArrayList<Type>();

        for (int index : operator_inputtedBy.get(operator_index)) {
            if(index >= 0) {
                this_args.add(evaluate(index));
            }else{
                this_args.add(args[-1-index]);
            }
        }
        return operators.get(operator_index).operation((Type[]) this_args.toArray());

    }

在什么情况下这个演员会失败(如果有的话)?什么是不需要铸造的替代品?

标签: javaarraylistcasting

解决方案


它总是失败。

我相信你想知道为什么。让我们试着解释一下:

与完全是编译器想象的泛型不同,数组确实知道它的“组件”类型。你可以问它:

String[] strings = new String[10];
Object[] o = strings; // compiles. This is itself problematic, see later.
System.out.println(o.getClass().getComponentType()); // prints 'java.lang.String'
o[0] = Integer.valueOf(0); // hmmmmmmmm!!!!!
String str0 = strings[0]; // ... but... wait.. that's an Integer, not a String!

把上面的东西扔到一个java文件中,它....

编译??

是的,它确实。它不应该,真的 - 它编译的原因,即使在今天,是 java 开始允许上述内容,并且 java 不喜欢破坏向后兼容性。该代码用于编译,所以它仍然可以。但这显然是不对的——我在一个字符串数组中放了一个整数。这就是为什么当您运行它时,它不起作用:您ArrayStoreException在将整数放入o[0].

让我们用泛型试试这个。同样的事情,只是,泛型:

List<String> strings = new ArrayList<String>();
List<Object> o = strings; // the evil occurs here
// hmmm, List doesn't appear to have a getComponentType()....
o.add(Integer.valueOf(0));
String str0 = strings.get(0);

现在,理智已经恢复:第二条线,邪恶发生在哪里?编译器不允许你这样做。因为它不应该,因为那样你会遇到这些片段显示的问题。在计算机科学中,数组赋值是协变的,而泛型是不变的。在现实世界中,这些东西是不变的(这就是为什么第一个片段会爆炸),所以泛型做对了,数组做错了,但我们无法修复它——java 应该是向后兼容的。

既然您知道了这一点,那么 toArray() 的问题就变得很清楚了:

  1. toArray() 方法不可能知道列表是什么组件类型;因此,如果您有 aList<String>并调用toArray()它,则 toArray 代码不可能String从自身获取,与数组不同,组件类型不被存储。泛型是编译器想象的虚构,它不在你的类文件中,只在你的源文件中。因此,它不知道,因此,它创建了一个对象数组(例如,从toArray()技术上讲,掉出的数组是一个对象,调用getComponentType它将始终 print class java.lang.Object

  2. String[]将 a 分配给是合法的Object[],因为 java 语言规范将数组定义为协变的。但现实生活并非如此。此外,语言规范还说反过来不成立:您不能将实例强制Object[]转换为 type String[]这就是为什么它总是会失败的原因——toArray()方法总是产生一个Object[],不能像那样强制转换。

  3. 但是假设您可以假设这样做:如果您随后将非字符串对象分配给数组的插槽之一,ArrayStoreException 则不会发生,因为组件类型不正确。

  4. 现在,当您重新读回它时,您会得到一个 classcastexception。

让我们尝试一下:

List<String> list = new ArrayList<String>();
list.add("Hello!");
String[] strings = (String[]) list.toArray(); // this fails...
Object[] o = strings;
o[0] = Integer.valueOf(5);
String str0 = strings[0]; // but otherwise you'd be in trouble here.

解决方案

修复很简单:

注意:在java中,我们写的是'operatorIndex',而不是'operator_index'。在罗马时,要像罗马人一样。

return operators.get(operatorIndex).operation(thisArgs.toArray(Type[]::new));

现在你没有收到任何警告,你不需要演员,一切都很好。在这种情况下,系统在运行时确实知道如何创建一个 Type 数组(因为你传入了一个 lambda 来创建它)。另一种选择是:

return operators.get(operatorIndex).operation(thisArgs.toArray(new Type[0]));

这里提供new Type[0]的数组(有合适的类型;它将是一个Type[],而不是一个Object[]

TL;DR:永远不要使用集合的 .toArray() 方法。它仍然存在,并将继续存在……因为我谈到的向后兼容性。这是唯一的原因。


推荐阅读