I am doing tasks from the Java SE 8 book by Cay S. Horstmann (Chapter 6 task 21)
Using the @SafeVarargs
annotation, write a method that allows you to build arrays of generalized types, as in the following example:
List<String>[] result = Arrays.<List<String> > construct (10) ;
// Sets the result in a list of type List<String>[] with a length of 10
My intended solution was such
static <T> T[] construct(int size) {
ArrayList<String> arr = new ArrayList<>();
for (int i = 0; i < size; i ) arr.add(null);
return (T[]) arr.toArray();
}
but it is incorrect since after erasing occurs
Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.util.List; ([Ljava.lang.Object; and [Ljava.util.List; are in module java.base of loader 'bootstrap') at Main.main(Main.java:23)
CodePudding user response:
It's a common mistake. arr.toArray()
returns Object[]
and then you try to cast it to List[]
array. Unlike generics the type of arrays is not removed during runtime. That's why you get a class cast exception.
CodePudding user response:
The @SafeVarargs
does already provide a hint towards the solution. Make the method a varargs method. Since a variable number of arguments also allows zero arguments, this change allows unchanged invocations:
public class Example
{
@SafeVarargs
static <T> T[] construct(int size, T... template) {
return Arrays.copyOf(template, size);
}
public static void main(String args[])
{
List<String>[] result = Example.<List<String>>construct(10);
System.out.println(result.getClass().getComponentType());
}
}
Note that you don’t need explicit type parameters here. The caller can be simplified to
List<String>[] result = Example.construct(10);
which implies that we can even omit the declaring class when being in the same class or using import static …
List<String>[] result = construct(10);
But when the caller does provide arguments to the varargs parameter, they may show up in the resulting array. To enforce an array with only null
elements, you could use
@SafeVarargs
static <T> T[] construct(int size, T... template) {
return Arrays.copyOf(Arrays.copyOf(template, 0), size);
}
or
@SafeVarargs
static <T> T[] construct(int size, T... template) {
T[] array = Arrays.copyOf(template, size);
Arrays.fill(array, null);
return array;
}
which can be tested with
List<String>[] result = construct(10, Collections.emptyList());
System.out.println(result.getClass().getComponentType());
System.out.println(Arrays.toString(result));
But note that the creation of generic arrays is not allowed for a reason. Consider
List<String>[] result = construct(10);
Object[] array = result;
array[0] = Arrays.asList(42);
List<String> stringList = result[0];
// this situation is called heap pollution
// at an entirely different place in you application, you might do
String s = stringList.get(0);
// without understanding what's going on