由于问题中提到了我,我会插话。
基本上,如果您不将此数组变量暴露给类外部,它不会引起任何问题。(有点像,在维加斯发生的事情留在维加斯。)
数组的实际运行时类型是Object[]
. 所以把它放入一个类型的变量Bar[]
实际上是一个“谎言”,因为Object[]
它不是Bar[]
(除非Object
是Bar
)的子类型。但是,如果它留在课堂内,这个谎言是可以的,因为Bar
它会被擦除到Object
课堂内。(这个问题的下限Bar
是Object
。如果下限Bar
是别的东西,请用Object
这个界限替换所有出现的讨论。)但是,如果这个谎言以某种方式暴露在外面(最简单的示例将bars
变量直接作为 type返回Bar[]
,那么它会导致问题。
要了解真正发生的事情,查看带有和不带有泛型的代码是有启发性的。任何泛型程序都可以重写为等效的非泛型程序,只需删除泛型并在正确的位置插入强制转换即可。这种转换称为类型擦除。
我们考虑一个简单的实现Foo<Bar>
,其中包含获取和设置数组中特定元素的方法,以及获取整个数组的方法:
class Foo<Bar> {
Bar[] bars = (Bar[])new Object[5];
public Bar get(int i) {
return bars[i];
}
public void set(int i, Bar x) {
bars[i] = x;
}
public Bar[] getArray() {
return bars;
}
}
// in some method somewhere:
Foo<String> foo = new Foo<String>();
foo.set(2, "hello");
String other = foo.get(3);
String[] allStrings = foo.getArray();
类型擦除后,变为:
class Foo {
Object[] bars = new Object[5];
public Object get(int i) {
return bars[i];
}
public void set(int i, Object x) {
bars[i] = x;
}
public Object[] getArray() {
return bars;
}
}
// in some method somewhere:
Foo foo = new Foo();
foo.set(2, "hello");
String other = (String)foo.get(3);
String[] allStrings = (String[])foo.getArray();
因此,班级内不再有演员表。但是,调用代码中有强制转换——当获取一个元素并获取整个数组时。获取一个元素的强制转换不应该失败,因为我们唯一可以放入数组的东西是Bar
,所以我们唯一可以取出的东西也是Bar
。但是,获取整个数组时的强制转换将失败,因为数组具有实际的运行时类型Object[]
。
以非通用的方式编写,正在发生的事情和问题变得更加明显。尤其令人不安的是,转换失败不会发生在我们用泛型编写转换的类中——它发生在使用我们类的其他人的代码中。而那个人的代码是完全安全和无辜的。它也不会发生在我们在泛型代码中进行强制转换的时候——它会在以后有人调用时发生getArray()
,而没有警告。
如果我们没有这个getArray()
方法,那么这个类是安全的。用这种方法,是不安全的。什么特点使它不安全?它bars
以 type的形式返回Bar[]
,这取决于我们之前所做的“谎言”。由于谎言不是真的,它会引起问题。如果该方法将数组返回为 type Object[]
,那么它将是安全的,因为它不依赖于“谎言”。
人们会告诉你不要进行这样的强制转换,因为它会在如上所示的意想不到的地方导致强制转换异常,而不是在未经检查的强制转换所在的原始位置。编译器不会警告你这getArray()
是不安全的(因为从它的角度来看,给定你告诉它的类型,它是安全的)。因此,程序员必须对这个陷阱保持警惕,不要以不安全的方式使用它。
但是,我认为这在实践中并不是一个大问题。任何设计良好的 API 都不会将内部实例变量暴露给外部。(即使有将内容作为数组返回的方法,它也不会直接返回内部变量;它会复制它,以防止外部代码直接修改数组。)因此getArray()
无论如何都不会实现任何方法。