2

I was wondering how to initialize the generic array in the constructor.

After searching on google, I wrote the code below.

public class Test<E>{
    private E[] mData;

    public Test(){
        mData = (E[])new Object[100];
    }

    public void put(E data, int index){
        mData[index]=data;
    }

    public E get(int index){
        return mData[index];
    }

    public static void main(String[] args){
        Test<Integer> t1 = new Test<>();
        t1.put(100, 0);
        System.out.println(t1.get(0)); // 100
    }
}

The output of the above code is expectedly 100, but if I access the generic array directly, it gives an error.

The code looks like below.

public class Test<E>{
    private E[] mData;

    public Test(){
        mData = (E[])new Object[100];
    }

    public static void main(String[] args){
        Test<Integer> t1 = new Test<>();
        t1.mData[0] = 100;
        System.out.println(t1.mData[0]);
        // java.lang.ClassCastException error
    }
}

The above code just gives me an ClassCastException error.

I have no idea what's the differences between those code I have uploaded.

Any help might really be appreciated.

iluxa
  • 6,941
  • 18
  • 36
jwkoo
  • 2,393
  • 5
  • 22
  • 35
  • @Nikolaus If you mean why "`new Object[100]`" that's because `new E[100]` is illegal. – Slaw Sep 27 '19 at 08:43
  • @Nikolaus because we cannot initialize the generic array with new E<>() since compiler does not know what the E is at the compilation time – jwkoo Sep 27 '19 at 08:43
  • 1
    @jwkoo You may also want to provide the stack trace of the `ClassCastException`, or at least the message. That said, my guess is when accessing the array directly there's an implicit cast to `Integer[]`, but since an `Object[]` is not an `Integer[]` you get the CCE. This is a symptom of the fact generics and arrays don't mix well. – Slaw Sep 27 '19 at 08:50
  • Object[] cannot cast to Integer[] directly. – aran Sep 27 '19 at 08:54
  • There is an answer for this question on StackOverflow at https://stackoverflow.com/questions/529085/how-to-create-a-generic-array-in-java – Ramesh Fadatare Sep 27 '19 at 08:57
  • @Slaw Thanks for your advice. But didn't I casted Object array to the Integer array at the constructor? – jwkoo Sep 27 '19 at 09:02
  • 1
    @jwkoo Your code should be emitting an "unchecked cast" warning, and situations like this are exactly why. All you've done is tell the compiler "treat this array as an `E[]`", but in reality you still have an `Object[]`. Remember, generics in Java are a compile-time only feature and arrays know their component type at runtime. – Slaw Sep 27 '19 at 09:03
  • Constructing the "generic" array as you do here [works only if you the array is not exposed](https://stackoverflow.com/a/2924453/545127). But you expose it. – Raedwald Sep 27 '19 at 09:20

2 Answers2

2

The important thing here is that type of mData is always Object[], even if it's masquerading as E[].

In your first example, the following happens under the hood:

    public E get(int index){
        Object x = mData[index];
        E result = (E) x;
        return result;
    }

In the second example however,

// ClassCast
Integer[] tempArray = t1.mData;
System.out.println(tempArray[0]);

If you actually want your array to be of type E[], use Array.newInstance(), but that does introduce an unnecessary argument to the constructor.

Your approach is more user-friendly (ArrayList, for example, does the same thing), just make sure to not expose the underlying array directly.

iluxa
  • 6,941
  • 18
  • 36
  • `ArrayList` actually uses `Object[]` internally and casts the elements "manually" when needed. – Slaw Sep 27 '19 at 09:00
  • @iluxa Thanks for your answer. What I am still wondering is that, I explicitly type casted Object array to E[] in the constructor. so I thought that when I call Test t1 = new Test<>(), I expected that the Integer generic array will be created. what am I still missing? thanks in advance. – jwkoo Sep 27 '19 at 09:00
  • 2
    The array that got created was still of `Object[]` type. Read up on "erasure": the generic parameter `E` gets re-written as `Object`. So effectively, `(E[])new Object[100]` is the same as `(Object[])new Object[100]`, which is allowed. If you actually wanted an `Integer` array, you'd need `Array.newInstance()` – iluxa Sep 27 '19 at 09:10
  • @jwkoo just be aware that casting (objects) does NOT change the instance type. (casting of primitives does change data) – user85421 Sep 27 '19 at 09:12
  • @iluxa thank you. As you've mentioned, if (E[])new Object[100] is the same as (Object[])new Object[100], then why we need E[] in front? sorry for too many questions. – jwkoo Sep 27 '19 at 09:24
  • 2
    @jwkoo To tell the compiler to treat it as an `E[]` and not an `Object[]`. The fact `(E[]) new Object[...]` is the same thing as `(Object[]) new Object[...]` only comes into play at _runtime_. – Slaw Sep 27 '19 at 09:25
  • 1
    What Slaw sad. Compiler thinks that `mData` is indeed `Integer[]`. Except as soon as you try and use it as `Integer[]`, compiler is happy, but it blows up at runtime. – iluxa Sep 27 '19 at 09:27
0

Because of type erasure, one needs to pass the actual Class<E>.

    public Test(Class<E> type) {
        mData = (E[]) Array.newInstance(type, 100);
    }

As Array.newInstance has a return type Object one needs an unsafe cast.

The reflection class Array then can create arrays. Also multidimensional:

(E[][][]) Array.newInstance(type, 3, 4, 5)

The same holds for creating instances of E, you need the class, and use a reflection Constructor.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • This provides a solution, but I believe the OP wants to know why the problem exists in the first place. In the second case there appears to be an implicit cast done on the array and type erasure, without any further explanation, doesn't satisfactorily answer why (at least not in my opinion). – Slaw Sep 27 '19 at 08:58
  • `Array` class found in `java.lang.reflect` – Laazo Sep 27 '19 at 09:04
  • @Slaw you are right, I paid only attention to _"I was wondering how to initialize the generic array in the constructor"_. I leave the answer for those searching that issue. – Joop Eggen Sep 27 '19 at 09:10