Error about creating array of Generic List in Java

maplemaple :

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];

TiiJ7 :

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.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=120598&siteId=1