Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error about creating array of Generic List in Java

First Code:

List<Integer>[] array = (List<Integer>[]) new Object[size]; 

It will give the following exception:

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')

Why is this wrong? I just follow the Effective Java Third Edition Page 132's method:

Second Code:

E[] array = (E[]) new Object[size];

However I found the following code works

Third Code:

List<Integer>[] array = (List<Integer>[]) new List[size];

My questions:

  1. Why first code is wrong but second code is suggested in Effective Java? Is there something I misunderstand?

For example: why the following code works well, but first code is wrong?

public class Test<E>{
    E[] array;
    public Test(){
        array = (E[]) new Object[10];
    }
    public E set(E x){
        array[0] = x;
        System.out.println(array[0]);
        return array[0];
    }

    public static void main(String[] args){
        Test<List<Integer>> test = new Test<>();
        List<Integer> list = new ArrayList<>();
        list.add(1);
        test.set(list);
    }
}
  1. Can anyone explain why third code is correct but the following code is wrong?

Fourth Code:

List<Integer>[] array = new List<Integer>[size];

like image 258
maplemaple Avatar asked Jul 12 '19 06:07

maplemaple


People also ask

Why can't you create a generic array in Java?

If generic array creation were legal, then compiler generated casts would correct the program at compile time but it can fail at runtime, which violates the core fundamental system of generic types.

Can you create an array of generic type in Java?

Java allows generic classes, methods, etc. that can be declared independent of types. However, Java does not allow the array to be generic. The reason for this is that in Java, arrays contain information related to their components and this information is used to allocate memory at runtime.

Is it possible to have a generic ArrayList of Java arrays?

Notice that we can't use generics while creating the array because java doesn't support generic array. So if we try to use below code, it will produce compile time error as “Cannot create a generic array of List<String>”.


1 Answers

First Code

List<Integer>[] array = (List<Integer>[]) new Object[size]; 

The reason why the first code fails is because casting does not change the actual type of the array, it just makes the compiler accept the code as valid. Imagine if you had another reference to the underlying object array:

final int size = 2;
Object[] objectArr = new Object[size];
List<Integer>[] integerArr = (List<Integer>[]) objectArr; // Does not work
objectArr[0] = "foobar";
List<Integer> i = integerArr[0]; // What would happen ??

The above code compiles fine, since you are forcing the compiler to accept it with the cast. But you can already see why it would be a problem for the cast to work at runtime: you would end up with an List<Integer>[] that now contains a String, which makes no sense. So the language disallows this.

Second Code

E[] array = (E[]) new Object[size];

Generics in Java are kind of odd. For various reasons, such as backwards compatibility, generics are basically erased by the compiler and will (mostly) not appear in the compiled code (Type Erasure). Instead, it will use a series of rules (JLS spec) to determine what type should be used in the code instead. For a basic unbouded generic; this type will be Object. So, assuming there is no bound on E, the second code is changed by the compiler to this:

 Object[] array = (Object[]) new Object[size];

So since both arrays have the exact same type after the erasure, there is no problem at runtime and the cast is basically redundant.

It is worth noting that this only works as long as E is unbounded. For example, this will fail at runtime with a ClassCastException:

public static <E extends Number> void genericMethod() {
    final int size = 5;
    E[] e = (E[]) new Object[size];
}

That is because E will be erased to Number, and you will get the same problem as the first code:

Number[] e = (Number[]) new Object[size];

It is important to keep the erasure in mind when working with code. Otherwise you may run into situations where the code acts different from what you expect. For example, the following code compiles and runs without exceptions:

public static <E> void genericMethod(E e) {
    final int size = 2;
    Object[] objectArr = new Object[size];
    objectArr[0] = "foobar";

    @SuppressWarnings("unchecked")
    E[] integerArr = (E[]) objectArr;
    integerArr[1] = e;

    System.out.println(Arrays.toString(integerArr));
    System.out.println(e.getClass().getName());
    System.out.println(integerArr.getClass().getName());
}

public static void main(String[] args) {
    genericMethod(new Integer(5)); // E is Integer in this case
}

Third Code

List<Integer>[] array = (List<Integer>[]) new ArrayList[size];

Similarly to the case above, the third code will be erased to the following:

 List[] array = (List[]) new ArrayList[size];

Which is no problem because ArrayList is a subtype of List.

Fourth Code

List<Integer>[] array = new ArrayList<Integer>[size];

The above will not compile. The creation of arrays with a type that has a generic type parameter is explicitely disallowed by the spec:

It is a compile-time error if the component type of the array being initialized is not reifiable (§4.7).

A type with a generic parameter that is not an unbounded wildcard (?) does not satisfy any condition for reifiability:

A type is reifiable if and only if one of the following holds:

  • 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.
like image 81
TiiJ7 Avatar answered Sep 23 '22 02:09

TiiJ7