有一个问题How to Convert List<String> to List<Object>,许多关于列表
的类似问题和带有C的列表扩展了P.
显然,我们不能像这样使用演员:
List<String> list0 = Arrays.asList("abc", "xyz");
List<Object> list1 = (List<Object>) list0; //ERROR here
上述问题中接受的答案是:
List<Object> objectList = new ArrayList<Object>(stringList);
但是当我尝试这段代码时:
List<String> list0 = Arrays.asList("abc", "xyz");
List<Object> list1 = (List<Object>) (Object) list0;
System.out.println(list1);
即便如此:
List<String> list0 = Arrays.asList("abc", "xyz");
List<Integer> list1 = (List<Integer>) (Object) list0;
System.out.println(list1);
它运行成功,所以我们可以间接投射!我真的很怀疑这个!任何可接受的理由?
答案 0 :(得分:1)
这是(或多或少)预期的。 Java泛型仅存在于编译器中,而不存在于运行时。编译器检查一切是否正常,然后擦除类型,只剩下Object
(或任何下限)。
如果您在此过程中转换为Object
,则您不再直接将一种泛型类型转换为另一种类型,并且编译器无法验证任何内容,因此它是允许的。由于泛型类型在运行时不再存在,因此也可以使用。但是您可能会使用该列表扰乱代码,因为您现在可以添加Object
而不仅仅是String
。当你只列举列表时,你所做的那种双重投射可能很好,但如果可能的话,我在任何一种情况下都不会这样做。类型安全是一件好事。
随着你的演员阵容List<Integer>
完全相同的事情正在发生。它仍然有效的原因是因为您只是打印列表(不确定是否枚举并在Java中列出列表的内容,但如果确实如此,那么它可能会调用toString()
无论如何,因为toString
是Object
上的方法,因此无论是在Integer
还是String
上调用它都没有区别。结果字节码是相同的。但是如果你改为调用Integer
的方法,那么你肯定会在运行时遇到崩溃,即使前面提到过的编译器也不能帮你做蠢事。
答案 1 :(得分:1)
写作:
List<Integer> list1 = (List<Integer>) (Object) list0;
相当于:
Object obj = (Object) list0;
List<Integer> list1 = (List<Integer>) obj;
就编译器而言,将任何引用类型转换为Object
始终有效(因为所有引用类型都是Object
s)。第二个强制转换也是允许的,因为就编译器而言,Object
引用可能(在运行时)保存对任何引用类型的引用,因此它允许您可以转换为任何类型(甚至到Set<Integer>
,它将通过编译并且只在运行时失败。
现在,在运行时,擦除泛型类型参数后,您的代码将变为:
List list0 = Arrays.asList("abc", "xyz");
List list1 = (List) (Object) list0;
System.out.println(list1);
两个强制转换在运行时都有效,因此您的代码不会导致ClassCastException
。
答案 2 :(得分:1)
从Generic<Subclass>
转换为Generic<SuperClass>
是不安全的,但大多数开发人员并不关心这一点。请查看以下示例类
class Fruit {
}
class Apple extends Fruit {
}
如果Apple
始终可以用作Fruit
,则Bowl<Apple>
不能用作Bowl<Fruit>
。如果我将Bowl<Apple>
投射到Bowl<Fruit>
,我可能会稍后在水果碗中添加Orange
,原来的苹果碗将包含橙色。例如:
Bowl<Apple> apples = new Bowl<Apple>();
...
Bowl<Fruit> fruit = (Bowl<Apple>) apples; // no exception
fruit.add(new Orange()); // no exception
...
Apple apple = apples.get(0); // no compiler warning, but exception
所以不允许演员的目的可能是提醒用户他可能正在做一些他不理解的事情。
然而,编译器允许我们将Fruit
转换为Apple
。这是Java的限制(其他语言只允许安全转换(使用类型检查确保转换是可能的)),但它并不像通用转换那样糟糕,因为在非法转换完成的位置抛出异常 - 使用通用转换时,即使在另一个类方法中也可能发生异常。
然而,在某些情况下,我们知道水果碗只包含苹果(也包括将来),或者列表将始终包含字符串(例如,如果数据不可修改)。在这种情况下,我更喜欢这个成语:
List<Object> list1 = (List<Object>) (List) list0;
答案 3 :(得分:0)
列表和列表不兼容。但你可以写:
List<Object> list1 = (List<Object>)(List<?>) list0;