java - 在什么情况下强制转换 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());
}
在什么情况下这个演员会失败(如果有的话)?什么是不需要铸造的替代品?
解决方案
它总是失败。
我相信你想知道为什么。让我们试着解释一下:
与完全是编译器想象的泛型不同,数组确实知道它的“组件”类型。你可以问它:
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() 的问题就变得很清楚了:
toArray() 方法不可能知道列表是什么组件类型;因此,如果您有 a
List<String>
并调用toArray()
它,则 toArray 代码不可能String
从自身获取,与数组不同,组件类型不被存储。泛型是编译器想象的虚构,它不在你的类文件中,只在你的源文件中。因此,它不知道,因此,它创建了一个对象数组(例如,从toArray()
技术上讲,掉出的数组是一个对象,调用getComponentType
它将始终 printclass java.lang.Object
。String[]
将 a 分配给是合法的Object[]
,因为 java 语言规范将数组定义为协变的。但现实生活并非如此。此外,语言规范还说反过来不成立:您不能将实例强制Object[]
转换为 typeString[]
。这就是为什么它总是会失败的原因——toArray()
方法总是产生一个Object[]
,不能像那样强制转换。但是假设您可以假设这样做:如果您随后将非字符串对象分配给数组的插槽之一,
ArrayStoreException
则不会发生,因为组件类型不正确。现在,当您重新读回它时,您会得到一个 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() 方法。它仍然存在,并将继续存在……因为我谈到的向后兼容性。这是唯一的原因。
推荐阅读
- firebase - Firebase Crashlytics 崩溃未报告给 Flutter 中的 Firebase 控制台
- node.js - 在 RESTful API 中检查用户权限的最佳实践
- javascript - Jquery .attr 返回未定义
- javascript - 检查一个数字是否以 00 结尾
- python - 如何在 Python 中进行导入
- reactjs - 如何使用来自组件的 Redux-hooks useDispatch 在操作中通过 API 调用调用函数?
- php - WC() 在 WooCommerce 3.7 中给出空结果
- talend - Talend 提取 Json 字符串作为键值数组
- azureservicebus - 将 WindowsAzure.ServiceBus Nuget 升级到最新版本后出错 - 客户端无法在指定的超时时间内完成操作
- sql - 子查询 Excel VBA SQL