I think that I understand the concept itself: the reified type of an array must be a subtype of the erasure of its static type. But I feel that looking around for help has only confused me more. Here's my understanding of it and why I'm confused.
Here is some code that converts a Collection<T> to an array of type T.
//This code is taken straight from the book Java Generics and Collections
import java.util.*;
class Wrong {
public static <T> T[] toArray(Collection<T> c) {
T[] a = (T[])new Object[c.size]; // unchecked cast
int i=0; for (T x : c) a[i++] = x;
}
public static void main(String[] args) {
List<String> strings = Arrays.asList("one", "two");
String[] a = toArray(strings); //class cast error
}
}
A ClassCastException is thrown. At compile time, erasure converts all occurrences of type T into type Object (because they have no bounds; if they did, they would be converted into their leftmost bound), converts parametric types into their raw types, and adds appropriate casts. So effectively, the code is equivalent to:
import java.util.*;
class Wrong {
public static Object[] toArray(Collection c) {
Object[] a = (Object[])new Object[c.size]; // unchecked cast
int i=0; for (Object x : c) a[i++] = x;
return a;
}
public static void main(String[] args) {
List strings = Arrays.asList(args);
String[] a = (String[])toArray(strings); //class cast error
}
}
Since the method toArray effectively returns an Object[], casting it to a String[] is illegal. In this case, the reified type of the array is Object while the erasure of its static type is String, disobeying the principle.
If I understand this correctly, it seems to me that this code should also fail, but actually works without error.
import java.util.*;
public class Right {
public static <T> T[] toArray(Collection<T> c, T[] a) {
if (a.length < c.size())
a = (T[])java.lang.reflect.Array.
newInstance(a.getClass().getComponentType(), c.size());
int i=0; for (T x : c) a[i++] = x;
if(i < a.length) a[i] = null;
return a;
}
public static void main(String[] args) {
List<String> strings = Arrays.asList("one", "two");
String[] a = toArray(strings, new String[0]);
assert Arrays.toString(a).equals("[one, two]");
String[] b = new String[] {"x","x","x","x"};
toArray(strings, b);
assert Arrays.toString(b).equals("[one, two, null, x]");
}
}
But following the first example, at compile time, through erasure, wouldn't all occurrences of type T be converted to type Object, parametric types be converted to raw types, and appropriate casts inserted just like the first example?
This is what I think it'd look like:
import java.util.*;
public class Right {
public static Object[] toArray(Collection c, Object[] a) {
if (a.length < c.size())
a = (Object[])java.lang.reflect.Array.
newInstance(a.getClass().getComponentType(), c.size());
int i=0; for (Object x : c) a[i++] = x;
if(i < a.length) a[i] = null;
return a;
}
public static void main(String[] args) {
List<String> strings = Arrays.asList("one", "two");
String[] a = (String[])toArray(strings, new String[0]); //class cast exception?????
assert Arrays.toString(a).equals("[one, two]");
String[] b = new String[] {"x","x","x","x"};
toArray(strings, b); /*I think this would be fine because the Principle of Truth in
Advertising is followed within the method body. It's just a problem
when attempting to assign the return value of this method within
the main method*/
assert Arrays.toString(b).equals("[one, two, null, x]");
}
}
But I assume that's not actually what's happening? To be clear, I feel that the cast to T[] on line 5 in both examples is what's causing the issue, as through erasure, T[] becomes Object[]. So, the array a is effectively of type Object[].
I feel like I have a fundamental misunderstanding about reification or erasure, or maybe something else. I've looked around online and no explanation has been able to satisfy my question, so answers would be greatly appreciated!