问题描述
好吧,我也读了不少这个问题的答案,但我有一个更具体的。以code的片断作为例子。
公共类GenericArray< E> {
E [] S =新E [5];
}
类型擦除后,就变成
公共类GenericArray {
[对象] S =新对象[5];
}
code的这个片段似乎运作良好。为什么它会导致编译时错误?
在此外,我已经从其他的答案已知以下codeS为同一目的很好地工作。
公共类GenericArray< E> {
E [] S =(E [])新对象[5];
}
我读过一些评论说,上面的那块code是不安全的,但为什么是不安全的?谁能给我提供的地方上面这段code会导致一个错误一个具体的例子?
此外,下面的code是错误的为好。但为什么?这似乎擦除之后很好地工作了。
公共类GenericArray< E> {
(E S)=新E();
}
First, because it would violate type safety (i.e. it is unsafe - see below), and in general code that can be statically determined to do this is not allowed to compile.
Remember that, due to type erasure, the type E
is not known at run-time. The expression new E[10]
could at best create an array of the erased type, in this case Object
, rendering your original statement:
E[] s= new E[5];
Equivalent to:
E[] s= new Object[5];
Which is certainly not legal. For instance:
String[] s = new Object[10];
... is not compilable, for basically the same reason.
You argued that after erasure, the statement would be legal, implying that you think this means that the original statement should also be considered legal. However this is not right, as can be shown with another simple example:
ArrayList<String> l = new ArrayList<Object>();
The erasure of the above would be ArrayList l = new ArrayList();
, which is legal, while the original is clearly not.
Coming at it from a more philosophical angle, type erasure is not supposed to change the semantics of the code, but it would do so in this case - the array created would be an array of Object
rather than an array of E
(whatever E
might be). Storing a non-E
object reference in it would then be possible, whereas if the array were really an E[]
, it should instead generate an ArrayStoreException
.
(Bearing in mind we are now talking about the case where E[] s= new E[5];
has been replaced with E[] s = (E[]) new Object[5];
)
It is unsafe (which in this instance is short for type unsafe) because it creates at run-time a situation in which a variable (s
) holds a reference to an object instance which is not a sub-type of the variable's declared type (Object[]
is not a subtype of E[]
, unless E
==Object
).
The essential problem is that it is possible to put non-E
objects into an array that you create by performing a cast (as in (E[]) new Object[5]
). For example, say there is a method foo
which takes an Object[]
parameter, defined as:
void foo(Object [] oa) {
oa[0] = new Object();
}
Then take the following code:
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...)
The array definitely contains String
objects even after the call to foo
. On the other hand:
E[] ea = (E[]) new Object[5];
foo(ea);
E e = ea[0]; // e may now refer to a non-E object!
The foo
method might have inserted a non-E
object into the array. So even though the third line looks safe, the first (unsafe) line has violated the constraints that guarantee that safety.
A full example:
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]; // returns the wrong type
}
}
class Other
{
public void callMe() {
Foo<String> f = new Foo<>();
String s = f.get(); // ClassCastException on *this* line
}
}
The code generates a ClassCastException when run, and it is not safe. Code without unsafe operations such as casts, on the other hand, cannot produce this type of error.
The code in question:
public class GenericArray<E>{
E s= new E();
}
After erasure, this would be:
Object s = new Object();
While this line itself would be fine, to treat the lines as being the same would introduce the semantic change and safety issue that I have described above, which is why the compiler won't accept it. As an example of why it could cause a problem:
public <E> E getAnE() {
return new E();
}
... because after type erasure, 'new E()' would become 'new Object()' and returning a non-E
object from the method clearly violates its type constraints (it is supposed to return an E
) and is therefore unsafe. If the above method were to compile, and you called it with:
String s = <String>getAnE();
... then you would get a type error at runtime, since you would be attempting to assign an Object
to a String
variable.
Further notes / clarification:
- Unsafe (which is short for "type unsafe") means that it could potentially cause a run-time type error in code that would otherwise be sound. (It actually means more than this, but this definition is enough for purposes of this answer).
- it's possible to cause a
ClassCastException
orArrayStoreException
or other exceptions with "safe" code, but these exceptions only occur at well defined points. That is, you can normally only get aClassCastException
when you perform a cast, an operation that inherently carries this risk. Similarly, you can only get anArrayStoreException
when you store a value into an array. - the compiler doesn't verify that such an error will actually occur before it complains that an operation is unsafe. It just knows that that certain operations are potentially able to cause problems, and warns about these cases.
- that you can't create a new instance of (or an array of) a type parameter is both a language feature designed to preserve safety and probably also to reflect the implementation restrictions posed by the use of type erasure. That is,
new E()
might be expected to produce an instance of the actual type parameter, when in fact it could only produce an instance of the erased type. To allow it to compile would be unsafe and potentially confusing. In general you can useE
in place of an actual type with no ill effect, but that is not the case for instantiation.
这篇关于为什么我不能用Java创建一个类型参数的数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!