10

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..!

Vistritium
  • 380
  • 2
  • 12

4 Answers4

6

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());
    }

}
Kylar
  • 8,876
  • 8
  • 41
  • 75
  • I don't seek the way of implement the class in better way. I just wonder why it throws exception when called directly and works good when through helper method. – Vistritium Sep 11 '13 at 17:01
  • notMagicalAtAll.arrayList.get(0) still fails, so that's not better. – Flyout91 Feb 24 '21 at 17:19
4

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.

Community
  • 1
  • 1
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • 1
    Actually it should fail at compile time, because you can't guarantee that Object is a T[]. – Kylar Sep 11 '13 at 16:46
  • The class works perfectly when not called directly, I use this in my code. It fails only on direct calls. – Vistritium Sep 11 '13 at 16:47
  • @Kylar compiler issues a warning – ZhongYu Sep 11 '13 at 16:47
  • @user2171129 that's the undefined part. – ZhongYu Sep 11 '13 at 16:47
  • Whether or not it works perfectly, you're using it wrong. You shouldn't create an array of Object if you're filling it with Integers, and especially not when you're trying to mask that with generics. I don't think you have a handle on how to use generics. – Kylar Sep 11 '13 at 16:49
  • Actually, I'm taking a course on Coursea "Algorithms, Part I" led by Princeton University and they suggest to use it like this. – Vistritium Sep 11 '13 at 16:56
  • Yeah... they're wrong. This is a place where Academics != Real World. – Kylar Sep 11 '13 at 16:57
  • 1
    There's nothing wrong with casting an array of objects to an array of a generified type so long as you can prove the cast is safe (and it helps to use `@SupressWarnings("unchecked")`) - Item 26 in _Effective Java_ by Josh Bloch. Also, the Collections API does this a lot. – sgbj Sep 11 '13 at 17:01
  • @user2171129 see JDK source code of `ArrayList`, it uses `Object[]` to store elements, not `E[]`. http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/ArrayList.java – ZhongYu Sep 11 '13 at 17:07
  • @sbat you cannot really create a generic array in java; it should be avoided if possible. – ZhongYu Sep 11 '13 at 17:08
  • @zhong.j.yu The purpose isn't to _create_ a generic array, the purpose is so that you can use the array throughout the rest of your code without explicit casts or fear of a ClassCastException. Whichever you choose is largely a matter of taste, but both are _fine_. And the ArrayList's toArray(T[]) method does such a cast. – sgbj Sep 11 '13 at 17:14
  • @zhong.j.yu They also do this: http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html#toArray(T[]) I wonder how did they accomplish that =) – Vistritium Sep 11 '13 at 17:15
1

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);
}
Arne Burmeister
  • 20,046
  • 8
  • 53
  • 94
1

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

newacct
  • 119,665
  • 29
  • 163
  • 224