【发布时间】:2011-08-17 16:31:24
【问题描述】:
这样做在语法上是合法的:
String [] s = new String[1];
Object [] o = s;
o[0] = new Integer(42);
当然它会在运行时崩溃。
我的问题是:首先允许这项任务有什么意义?
【问题讨论】:
这样做在语法上是合法的:
String [] s = new String[1];
Object [] o = s;
o[0] = new Integer(42);
当然它会在运行时崩溃。
我的问题是:首先允许这项任务有什么意义?
【问题讨论】:
问题是分配Object [] o = s; - 我想这就是你所说的“这个”的意思。
技术术语是array covariance,如果没有它,您就无法编写通用处理数组的代码。例如,java.util.Arrays 中的大多数非原始数组方法将毫无用处,因为您只能将它们与实际的 Object[] 实例一起使用。显然,Java 的设计者认为这比完全的类型安全更重要。
在查看 Java 5 中引入的 Java 泛型时,您会看到另一种解决方案:通过通配符显式协变。然而,这会导致相当大的复杂性(请参阅关于? 通配符的源源不断的问题),Java 的原始设计者希望避免复杂性。
【讨论】:
允许这种分配的意义在于禁止它会使以下事情变得不可能:
ArrayList[] lists = new ArrayList[10];
lists[0] = new ArrayList();
List[] genericLists = lists;
lists[0].add("someObject");
如果编译器禁止您的 String -> Object 情况,那么它还必须禁止 ArrayList -> List 以及从子类分配给其超类类型之一的任何其他实例。这使得 Java 等面向对象语言的许多特性变得毫无用处。当然,更典型的做法是:
List[] lists = new List[10];
lists[0] = new ArrayList();
lists[0].add("someObject");
但无论如何,编译器无法过滤掉这些情况,同时不允许许多有用且合法的用例,因此程序员有责任确保他们所做的事情是理智的。如果你想要的是Object[],那么就这样声明你的变量。如果你声明某个东西为String[],将其转换为Object[],然后忘记你真正拥有的是String[],那么这只是程序员错误。
【讨论】:
这是不允许的,当你运行该代码时你会得到一个java.lang.ArrayStoreException: java.lang.Integer
编译器允许这样做,因为您将 String[] 转换为 Object[],这是正确的。这类似于
Integer i = new Integer(10);
Object o = i;
String s = (String) o;
编译器不会抱怨,但您会在运行时收到 ClassCastExeption。
【讨论】:
编译器无法知道(没有静态分析)o 实际上是String[],因此即使在运行时失败也允许赋值。没有什么可以阻止例如Integer[] 被分配给o,它只是不会发生。
【讨论】:
必须允许赋值,因为编译器无法推断数组在运行时是否只保存(或不保存)字符串。它会抛出一个[ArrayStoreException][1],它会在运行时进行检查。
考虑一下:
String [] s = new String[1];
Object [] o = s;
o = new Integer[1];
o[0] = new Integer(1);
这种情况是有效的并且运行正常。为了提供更广泛的视角,恕我直言,数组是 Java 中的低级泄漏抽象。
【讨论】:
通常编译器无法判断o 是否已分配为String[]。考虑一下:
String[] s = new String[1];
Object[] o;
if (complexFunction(System.currentTimeMillis())) {
o = s;
} else {
o = new Integer[1];
}
o[0] = 42;
编译器在设计时不会知道o 将采用什么类型 - 所以它只允许赋值。
【讨论】: