Reputation: 595
Well, I have read a lot of answers to this question, but I have a more specific one. Take the following snippet of code as an example.
public class GenericArray<E>{
E[] s= new E[5];
}
After type erasure, it becomes
public class GenericArray{
Object[] s= new Object[5];
}
This snippet of code seems to work well. Why does it cause a compile-time error?
In addition, I have known from other answers that the following codes work well for the same purpose.
public class GenericArray<E>{
E[] s= (E[])new Object[5];
}
I've read some comments saying that the piece of code above is unsafe, but why is it unsafe? Could anyone provide me with a specific example where the above piece of code causes an error?
In addition, the following code is wrong as well. But why? It seems to work well after erasure, too.
public class GenericArray<E>{
E s= new E();
}
Upvotes: 17
Views: 2045
Reputation: 20631
This snippet of code seems to work well. Why does it cause a compile-time error?
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
.
why is it unsafe?
(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
).
Could anyone provide me with a specific example where the above piece of code causes an error?
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.
In addition, the following code is wrong as well. But why? It seems to work well after erasure, too.
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:
ClassCastException
or ArrayStoreException
or other exceptions with "safe" code, but these exceptions only occur at well defined points. That is, you can normally only get a ClassCastException
when you perform a cast, an operation that inherently carries this risk. Similarly, you can only get an ArrayStoreException
when you store a value into an array.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 use E
in place of an actual type with no ill effect, but that is not the case for instantiation.Upvotes: 3
Reputation: 7035
Let's say generic arrays are allowed in Java. Now, take a look at following code,
Object[] myStrs = new Object[2];
myStrs[0] = 100; // This is fine
myStrs[1] = "hi"; // Ambiguity! Hence Error.
If user is allowed to create generic Array, then user can do as I've shown in above code and it will confuse compiler. It defeats the purpose of arrays (Arrays can handle only same/similar/homogeneous type of elements
, remember?). You can always use array of class/struct if you want heterogeneous array.
More info here.
Upvotes: 0
Reputation: 81123
A compiler can use a variable of type Object
to do anything a variable of type Cat
can do. The compiler may have to add a typecast, but such typecast will either throw an exception or yield a reference to an instance of Cat
. Because of this, the generated code for a SomeCollection<T>
doesn't have to actually use any variables of type T
; the compiler can replace T
with Object
and cast things like function return values to T
where necessary.
A compiler cannot use an Object[]
, however, to do everything a Cat[]
can do. If a SomeCollection[]
had an array of type T[]
, it would not be able to create an instance of that array type without knowing the type of T
. It could create an instance of Object[]
and store references to instances of T
in it without knowing the type of T
, but any attempt to cast such an array to T[]
would be guaranteed to fail unless T
happened to be Object
.
Upvotes: 2
Reputation: 106390
Array declarations are required to have a reifiable type, and generics are not reifiable.
From the documentation: the only type you can place on an array is one that is reifiable, that is:
It refers to a non-generic class or interface type declaration.
It is a parameterized type in which all type arguments are unbounded wildcards (§4.5.1).
It is a raw type (§4.8).
It is a primitive type (§4.2).
It is an array type (§10.1) whose element type is reifiable.
It is a nested type where, for each type T separated by a ".", T itself is reifiable.
This means that the only legal declaration for a "generic" array would be something like List<?>[] elements = new ArrayList[10];
. But that's definitely not a generic array, it's an array of List
of unknown type.
The main reason that Java is complaining about the you performing the cast to E[]
is because it's an unchecked cast. That is, you're going from a checked type explicitly to an unchecked one; in this case, a checked generic type E
to an unchecked type Object
. However, this is the only way to create an array that is generic, and is generally considered safe if you have to use arrays.
In general, the advice to avoid a scenario like that is to use generic collections where and when you can.
Upvotes: 7