Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic array throws ClassCastException when referenced directly (it doesn't when calling through helper method)

This is magic! Look at this simple code:

public class ArrayOFMagic<T> {

    protected T[] array;

    protected int showMeYouRLength() {
        return array.length;
    }

    ArrayOFMagic() {
        array = (T[]) new Object[10];
    }

    protected void set(T value, int index) {
        array[index] = value;
    }

    public static void main(String[] args) {
        ArrayOFMagic<Integer> arrayOFMagic = new ArrayOFMagic<Integer>();
        System.out.println(arrayOFMagic.showMeYouRLength());
        System.out.println("MAGIC INCOMING");
        System.out.println(arrayOFMagic.array.length);
    }

}

Output:

10
MAGIC INCOMING
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; at ArrayOFMagic.main(ArrayOFMagic.java:25)

I call array.length two times. Once through method and once directly. It proceeds when using method and throws exception when called directly. O.o Someone explain?

edit: Just to clarify: Class works good when not called directly. You can have setters/getters on array elements and all..!

like image 412
Vistritium Avatar asked Sep 11 '13 16:09

Vistritium


People also ask

How to initialize generic array Java?

reflect. Array#newInstance to initialize our generic array, which requires two parameters. The first parameter specifies the type of object inside the new array. The second parameter specifies how much space to create for the array.

What causes ClassCastException?

ClassCastException is a runtime exception raised in Java when we try to improperly cast a class from one type to another. It's thrown to indicate that the code has attempted to cast an object to a related class, but of which it is not an instance.

Can we use generics with array 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.

How to create array of generic Class in Java?

You can do this: E[] arr = (E[])new Object[INITIAL_ARRAY_LENGTH]; This is one of the suggested ways of implementing a generic collection in Effective Java; Item 26. No type errors, no need to cast the array repeatedly.


4 Answers

UPDATED Answer:

DISCLAIMER: This answer is not from me, I asked a former Sun employee who worked on generics in Java why this happens. His answer:

The first call accesses the member from inside the generic class itself, which is erased to it's lower bound (in this case, Object), so there's no cast in the ref to array.length.

But the second call is on a parameterized instance of a generic type, so the type variable (the array) is bound to Integer.

The array field is declared to be of type T[], which is bound to Integer[]. The accessor code that is emitted by the compiler casts it to that type, and the cast blows (in every sense of the word.)

OLD ANSWER: Here's a better way to implement your class (as an addition to zhong.j.yu's answer).

import java.util.ArrayList;
import java.util.List;

public class NotSoMagical<T> {

    public List<T> arrayList;

    protected int length() {
        return arrayList.size();
    }

    NotSoMagical() {
        arrayList = new ArrayList<T>(10);
    }

    protected void set(T value, int index) {
        arrayList.set(index, value);
    }

    public static void main(String[] args) {
        NotSoMagical<Integer> notMagicalAtAll = new NotSoMagical<Integer>();
        System.out.println(notMagicalAtAll.length());
        System.out.println("MAGIC INCOMING");
        System.out.println(notMagicalAtAll.arrayList.size());
    }

}
like image 180
Kylar Avatar answered Oct 01 '22 16:10

Kylar


Ideally, this line should fail immediately at runtime

    array = (T[]) new Object[10];   // Object[] cannot be cast to Integer[]

unfortunately, due to erasure, the error is swallowed. The error could pop up later, but when and where? That's undefined. The language spec does not say whether access to arrayOFMagic.array.length should succeed. See also: Generics Oddity - I can insert a Long value into a Map<String, String> and it compiles and doesn't fail at runtime

Suggestion: do not use T[] array; use simply Object[] array.

like image 25
ZhongYu Avatar answered Sep 29 '22 16:09

ZhongYu


When using a generic type, the compiler creates hidden casts, so the exception occurs due to wrong array creation. But you may create the array with the right type by changing the constructor using java.lang.reflect.Array:

ArrayOFMagic(Class<T> elementType) {
    array = (T[]) Array.newInstance(elementType, 10);
}
like image 24
Arne Burmeister Avatar answered Oct 01 '22 16:10

Arne Burmeister


It is most instructive to write this without generics. Any program written with generics can be converted to an equivalent program by just removing generics and adding casts. This conversion is called type erasure. After type erasure, what is happening becomes very apparent:

public class ArrayOFMagic {

    protected Object[] array;

    protected int showMeYouRLength() {
        return array.length;
    }

    ArrayOFMagic() {
        array = new Object[10];
    }

    protected void set(Object value, int index) {
        array[index] = value;
    }

    public static void main(String[] args) {
        ArrayOFMagic arrayOFMagic = new ArrayOFMagic();
        System.out.println(arrayOFMagic.showMeYouRLength());
        System.out.println("MAGIC INCOMING");
        System.out.println(((Integer[])arrayOFMagic.array).length);
    }

}

(In this case, you could argue that the cast to Integer[] is unnecessary. That a smart compiler could eliminate it, since Object[] already has the field named length. However, as a general rule, whenever you get something from the generic class into the "outside" of the generic scope, it is cast to the appropriate type with the type argument substituted in.)

like image 42
newacct Avatar answered Oct 02 '22 16:10

newacct