This question has to do with type erasure and its implication on arrays SCJP (by Mughal – 3rd edition on page 726). Here’s the code in short:
public class MyStack<E> implements IStack<E> {
// Top of stack.
private Node<E> tos;
// Size of stack
private int numOfElements;
:
// Incorrect version
public E[] toArray3() {
E[] toArray = (E[])new Object[numOfElements];
int i=0;
for (E data : this) {
toArray[i++] = data;
}
return toArray;
}
// Correct version
public E[] toArray4(E[] toArray) {
if (toArray.length != numOfElements) {
toArray = E[])java.lang.reflect.Array.newInstance(toArray.getClass().getComponentType(), numOfElements);
}
int i=0;
for (E data : this) {
toArray[i++] = data;
}
return toArray;
}
}
According to the book, method toArray3 is the incorrect version to use while method toArray4 is the correct version since array is of reifiable type. I don’t have problem on this. However, I have a problem when it comes to understanding why type parameter E is different for toArray3 and toArray4.
Here’s how I found out. Supposedy I have the following:
MyStack<Integer> intStack = new MyStack<Integer>();
intStack.push(9);
Integer[] x = intStack.toArray4(new Integer[0]); // clause 1
System.out.println(x.length);
Integer[] y = intStack.toArray4(new Object[0]); // clause 2 - compiler error
System.out.println(y.length);
Integer[] z = intStack.toArray3(); // clause 3
System.out.println(z.length);
a. For clause 1, no problem. The type parameter E in toArray4 seems to be of Integer.
b. For clause 2, the error is saying that Integer[] is not applicable for Object[]. Does that mean that type erasure of toArray4(E[] toArray) is toArray4[Integer[] toArray)? Why not Object[]? (Though I know that intStack has been instiantiated with Integer.)
c. For clause 3, the runtime error is that saying Object[] cannot be assigned to Integer[]. I do understand this, but why now is the type parameter E on toArray3 Object and not Integer?
I would expect the type parameter E ON toArray3 to be the same as toArray4 since type parameter E itself for intStack is of integer type which are used by both toArray3 AND toArray4.
Please bear in mind that in the body of toArray3, the array Object has been cast back to E[].
The difference between these methods is that
toArray4()is using reflection to obtain theClassobject that represents the correct component type of the array. This ensures that the type information is known at runtime in spite of type erasure.The key is this line of
toArray4():Unlike generic objects, arrays can provide their component type, since something like
Integer[]has a correspondingClassobject that can be inspected for this information.Because of this,
toArray4()actually instantiates anInteger[]object whenEisInteger, whereastoArray3()instantiates anObject[]and the calling code (through type erasure) attempts to cast it toInteger[], rightly causing aClassCastException.In general, reflection is often used as a workaround to type erasure.
As a note, arrays in Java tend to have certain “magic” associated with them, especially with regard to reflection. For example if you look at the source for
Class#getComponentType()andArray.newInstance()they both lead to native code.EDIT: I think the source of your confusion is the cast being done in
toArray3():This is not the cast that is causing your
ClassCastException. Rather it’s a second cast which is added by type erasure. To illustrate, let’s rewritetoArray3()as if type erasure had been applied:Then in the calling code:
As you can see, the
ClassCastExceptionis thrown not in the method, but when its result is assigned tozusing a cast, which type erasure creates for you. We might verify this if you actually post the stacktrace.