这段代码片段看起来很好用。为什么它会导致编译时错误?
首先,因为它会违反类型安全性(即它是不安全的-见下文),通常静态确定会这样做的代码是不允许编译的。
请记住,由于类型擦除,类型E在运行时是未知的。表达式new E [10]最多只能创建一个被擦除类型的数组,在本例中是Object,从而使您原始的语句无效:
E[] s= new E[5];
等同于:
E[] s= new Object[5]
这显然是不合法的。例如:
String[] s = new Object[10]
“...不可编译,基本上是因为同样的原因。”
你认为在类型擦除后,该语句将是合法的,这暗示了你认为原始语句也应被视为合法。然而,另一个简单的例子可以证明这是不正确的:
ArrayList<String> l = new ArrayList<Object>()
上述代码的擦除将会是合法的
ArrayList l = new ArrayList();
,而原始代码显然不合法。
从更哲学的角度来看,类型擦除不应该改变代码的语义,但在这种情况下它确实会改变——创建的数组将是一个
Object
数组,而不是一个
E
数组(无论
E
是什么)。因此,在其中存储非
E
对象引用将成为可能,而如果数组真的是
E[]
,则应该生成一个
ArrayStoreException
。
“为什么它不安全?”(请记住,我们现在讨论的是将
E[] s= new E[5];
替换为
E[] s = (E[]) new Object[5];
的情况。)
这是不安全的(在这种情况下是指类型不安全),因为它在运行时创建了一种情况,即一个变量(
s
)持有对一个对象实例的引用,而该对象实例不是变量声明类型的子类型(
Object[]
不是
E[]
的子类型,除非
E
==
Object
)。
“能否提供一个特定的例子,说明上述代码会导致错误?”
基本问题在于,通过执行强制类型转换(如
(E[]) new Object[5]
)创建的数组中可以放置非
E
对象。例如,假设有一个接受
Object[]
参数的方法
foo
,定义为:
void foo(Object [] oa) {
oa[0] = new Object();
}
然后使用以下代码:
String [] sa = new String[5];
foo(sa);
String s = sa[0]; // If this line was reached, s would
// definitely refer to a String (though
// with the given definition of foo, this
// line won't be reached...)
即使调用foo
方法后,数组中仍然包含String
对象。另一方面:
E[] ea = (E[]) new Object[5];
foo(ea);
E e = ea[0]; // e may now refer to a non-E object!
foo
方法可能向数组中插入了一个非
E
对象。因此,尽管第三行看起来是安全的,但第一行(不安全的)已经违反了保证安全性的约束条件。
一个完整的例子:
class Foo<E>
{
void foo(Object [] oa) {
oa[0] = new Object();
}
public E get() {
E[] ea = (E[]) new Object[5];
foo(ea);
return ea[0];
}
}
class Other
{
public void callMe() {
Foo<String> f = new Foo<>();
String s = f.get();
}
}
当运行代码时,会生成ClassCastException,并且不安全。另一方面,没有不安全操作(如强制转换)的代码不能产生这种类型的错误。
此外,以下代码也是错误的。但是为什么?它在擦除后似乎也能正常工作。
有问题的代码:
public class GenericArray<E>{
E s= new E();
}
擦除后,这将是:
Object s = new Object();
虽然这一行本身是可以的,但如果将它们视为相同的行,则会引入我上面描述的语义变化和安全问题,这就是编译器不接受它的原因。以下是可能会引起问题的示例:
public <E> E getAnE() {
return new E();
}
由于类型擦除后,'new E()'将变为'new Object()',从方法返回非 E
对象显然违反了其类型约束(它应该返回一个E
),因此是不安全的。如果上述方法编译通过,并且您使用以下方式调用它:
String s = <String>getAnE()
如果你试图将一个Object
赋值给一个String
变量,那么在运行时你会得到一个类型错误。
进一步的说明:
- 不安全(缩写为“类型不安全”)意味着它可能会在本来是正确的代码中导致运行时类型错误。(实际上它的含义更多,但这个定义足以回答本问题)。
- 即使使用“安全”的代码也有可能引发
ClassCastException
或ArrayStoreException
等异常,但这些异常只会在明确定义的点发生。也就是说,通常只有在进行强制类型转换时才会出现ClassCastException
,而在数组中存储值时才会出现ArrayStoreException
。
- 编译器并不验证这种错误是否真的会在运行时发生,它只知道某些操作潜在地可能会导致问题,并警告这些情况。
- 不能创建一个类型参数的新实例(或数组)既是一种设计用于保持安全性的语言特性,也可能反映了使用类型擦除所带来的实现限制。也就是说,
new E()
可能被期望产生实际类型参数的实例,而实际上它只能产生擦除类型的实例。允许它编译将是不安全和可能令人困惑的。通常你可以在实际类型的位置使用E
而不会有任何负面影响,但在实例化时情况并非如此。
E[] s= (E[])new Object[5];
,你尝试过String[] arr = new GenericArray<String>().s;
吗? - Radiodef