由于我在问题中被提到,我会发表一下我的看法。
基本上,如果你不将这个数组变量暴露给类的外部,它就不会引起任何问题。(有点像,“拉斯维加斯发生的事情,留在拉斯维加斯。”)
该数组的实际运行时类型是Object[]
。因此,将其放入Bar[]
类型的变量中实际上是一种“谎言”,因为除非Object
是Bar
,否则Object[]
不是Bar[]
的子类型。然而,如果这个谎言只在类内部存在,那么这是可以的,因为在类内部,Bar
被擦除成Object
。(在这个问题中,Bar
的下界是Object
。如果Bar
的下界是其他值,在本讨论中将所有出现的Object
替换为该值即可。)但是,如果这个谎言被某种方式暴露给外部(最简单的例子是直接将bars
变量作为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;
}
}
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;
}
}
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
作为类型
Bar []
返回,这取决于我们之前所说的“谎言”。由于这个谎言不是真的,所以会引起问题。如果该方法改为以
Object []
类型返回数组,则它将是安全的,因为它不依赖于这个“谎言”。
人们会告诉你不要像这样进行转换,因为它会在意想不到的地方引起转换异常,就像上面所看到的那样,并不是在未经检查的转换发生的原始位置。编译器不会警告您
getArray()
不安全(因为从它的角度来看,根据您告诉它的类型,它是安全的)。因此,这取决于程序员勤奋地避免这种陷阱并不以不安全的方式使用它。
然而,我认为实际中并不是一个大问题。任何设计良好的API都不会将内部实例变量暴露给外部。 (即使有一种方法可以将内容作为数组返回,它也不会直接返回内部变量;它会对其进行复制,以防止外部代码直接修改数组。)因此,任何方法都不会像
getArray()
一样实现。
Object bar = foo.getArray()[0]
会失败,而for (Object bar: foo.getArray())
却可以正常工作...但我想这只是由于两个语句转换数组的位置和方式不同造成的...谢谢!+1,v´ - Markus A.