86

I don't understand the connection between generics and arrays.

I can create array reference with generic type:

private E[] elements; //GOOD

But can't create array object with generic type:

elements = new E[10]; //ERROR

But it works:

elements = (E[]) new Object[10]; //GOOD
Rohit Jain
  • 195,192
  • 43
  • 369
  • 489
user2693979
  • 2,212
  • 4
  • 16
  • 22

4 Answers4

205

You should not mix-up arrays and generics. They don't go well together. There are differences in how arrays and generic types enforce the type check. We say that arrays are reified, but generics are not. As a result of this, you see these differences working with arrays and generics.

Arrays are covariant, Generics are not:

What that means? You must be knowing by now that the following assignment is valid:

Object[] arr = new String[10];

Basically, an Object[] is a super type of String[], because Object is a super type of String. This is not true with generics. So, the following declaration is not valid, and won't compile:

List<Object> list = new ArrayList<String>(); // Will not compile.

Reason being, generics are invariant.

Enforcing Type Check:

Generics were introduced in Java to enforce stronger type check at compile time. As such, generic types don't have any type information at runtime due to type erasure. So, a List<String> has a static type of List<String> but a dynamic type of List.

However, arrays carry with them the runtime type information of the component type. At runtime, arrays use Array Store check to check whether you are inserting elements compatible with actual array type. So, the following code:

Object[] arr = new String[10];
arr[0] = new Integer(10);

will compile fine, but will fail at runtime, as a result of ArrayStoreCheck. With generics, this is not possible, as the compiler will try to prevent the runtime exception by providing compile time check, by avoiding creation of reference like this, as shown above.

So, what's the issue with Generic Array Creation?

Creation of array whose component type is either a type parameter, a concrete parameterized type or a bounded wildcard parameterized type, is type-unsafe.

Consider the code as below:

public <T> T[] getArray(int size) {
    T[] arr = new T[size];  // Suppose this was allowed for the time being.
    return arr;
}

Since the type of T is not known at runtime, the array created is actually an Object[]. So the above method at runtime will look like:

public Object[] getArray(int size) {
    Object[] arr = new Object[size];
    return arr;
}

Now, suppose you call this method as:

Integer[] arr = getArray(10);

Here's the problem. You have just assigned an Object[] to a reference of Integer[]. The above code will compile fine, but will fail at runtime.

That is why generic array creation is forbidden.

Why typecasting new Object[10] to E[] works?

Now your last doubt, why the below code works:

E[] elements = (E[]) new Object[10];

The above code have the same implications as explained above. If you notice, the compiler would be giving you an Unchecked Cast Warning there, as you are typecasting to an array of unknown component type. That means, the cast may fail at runtime. For e.g, if you have that code in the above method:

public <T> T[] getArray(int size) {
    T[] arr = (T[])new Object[size];        
    return arr;
}

and you call invoke it like this:

String[] arr = getArray(10);

this will fail at runtime with a ClassCastException. So, no this way will not work always.

What about creating an array of type List<String>[]?

The issue is the same. Due to type erasure, a List<String>[] is nothing but a List[]. So, had the creation of such arrays allowed, let's see what could happen:

List<String>[] strlistarr = new List<String>[10];  // Won't compile. but just consider it
Object[] objarr = strlistarr;    // this will be fine
objarr[0] = new ArrayList<Integer>(); // This should fail but succeeds.

Now the ArrayStoreCheck in the above case will succeed at runtime although that should have thrown an ArrayStoreException. That's because both List<String>[] and List<Integer>[] are compiled to List[] at runtime.

So can we create array of unbounded wildcard parameterized types?

Yes. The reason being, a List<?> is a reifiable type. And that makes sense, as there is no type associated at all. So there is nothing to loose as a result of type erasure. So, it is perfectly type-safe to create an array of such type.

List<?>[] listArr = new List<?>[10];
listArr[0] = new ArrayList<String>();  // Fine.
listArr[1] = new ArrayList<Integer>(); // Fine

Both the above case is fine, because List<?> is super type of all the instantiation of the generic type List<E>. So, it won't issue an ArrayStoreException at runtime. The case is same with raw types array. As raw types are also reifiable types, you can create an array List[].

So, it goes like, you can only create an array of reifiable types, but not non-reifiable types. Note that, in all the above cases, declaration of array is fine, it's the creation of array with new operator, which gives issues. But, there is no point in declaring an array of those reference types, as they can't point to anything but null (Ignoring the unbounded types).

Is there any workaround for E[]?

Yes, you can create the array using Array#newInstance() method:

public <E> E[] getArray(Class<E> clazz, int size) {
    @SuppressWarnings("unchecked")
    E[] arr = (E[]) Array.newInstance(clazz, size);

    return arr;
}

Typecast is needed because that method returns an Object. But you can be sure that it's a safe cast. So, you can even use @SuppressWarnings on that variable.

Rohit Jain
  • 195,192
  • 43
  • 369
  • 489
  • Nitpick: "Creation of array whose component type is ... a wildcard parameterized type, is type-unsafe." Actually instantiating e.g. a `new List>[] { }` is valid - it's just that the wildcard can't be bounded. – Paul Bellora Sep 02 '13 at 23:46
  • Also, "this will fail at runtime with a ClassCastException." is not exactly true. When a cast is unchecked it means that it *won't* fail fast. Instead, ClassCastExceptions *may* be thrown in other places, where the compiler has inserted casts during the process of erasure. [This issue](http://stackoverflow.com/questions/18454854/casting-between-an-object-of-generic-type-t-and-an-object-of-generic-type-t) is a good example. – Paul Bellora Sep 03 '13 at 00:25
  • @PaulBellora. Actually, I meant bounded. Missed the word. Will edit thanks :) – Rohit Jain Sep 03 '13 at 04:55
  • @PaulBellora. As for casting part, I wrote that for casting to `String[]`, that will certainly fail. Edited that part to make it clear. – Rohit Jain Sep 03 '13 at 04:57
  • Oh, generics were so badly implemented in Java. – IS4 Feb 23 '16 at 20:48
  • "Since the type of T is not known at runtime, the array created is actually an Object[]". Why is it not known at runtime? How am I wrong to suppose its known even at compile time? – Klaus Jun 10 '16 at 11:38
  • @Klaus I'm not sure I got your question? Do you want to know why it is not known at runtime, or why it is known at compile time? – Rohit Jain Jun 10 '16 at 18:40
  • My first question is: I wonder why it is not known at run time? – Klaus Jun 11 '16 at 06:45
  • @Klaus Because of type-erasure. http://stackoverflow.com/questions/339699/java-generics-type-erasure-when-and-what-happens – Asif Mushtaq Aug 05 '16 at 19:37
  • @RohitJain Why new T[] could not be converted to given Argument? like new String[] or something else? instead of `Object[]`? – Asif Mushtaq Aug 05 '16 at 19:41
  • @RohitJain I know there is a performance penalty for using reflection. Can we know in this case, if it's native method? I guess it depends, hm? – Eel Lee Jul 31 '17 at 14:29
3

Here is the implementation of LinkedList<T>#toArray(T[]):

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size);
    int i = 0;
    Object[] result = a;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;

    if (a.length > size)
        a[size] = null;

    return a;
}

In short, you could only create generic arrays through Array.newInstance(Class, int) where int is the size of the array.

Josh M
  • 10,457
  • 7
  • 37
  • 46
3

Problem is that while runtime generic type is erased so new E[10] would be equivalent to new Object[10].

This would be dangerous because it would be possible to put in array other data than of E type. That is why you need to explicitly say that type you want by either

Pshemo
  • 113,402
  • 22
  • 170
  • 242
  • But (E[]) will not convert into (O[]) by the type erasure? – user2693979 Sep 02 '13 at 21:53
  • @user2693979 It will. Generics are compiler tool not runtime. I don't quite get what problem you are trying to show... – Pshemo Sep 02 '13 at 22:02
  • @user2693979 You should accept Rohit`s answer if you consider it better. No pressure only because I posted mine little earlier. – Pshemo Sep 02 '13 at 22:17
  • But if E[] will be Object[] and (E[]) will be (Object[]), then why is difference between (e = new E[10]) and (e = (E[]) Object[10])? Will not both be e = new Object[10]? – user2693979 Sep 02 '13 at 22:20
  • @user2693979 I suspect that `new E[size]` is not allowed to prevent thinking that we are actually creating array of `E` type and not `Object` type. Ways I mentioned in my answer clearly shows what is going on, while `new E[size]` can be interpreted incorrectly. But again, that is only my suspicions. – Pshemo Sep 02 '13 at 22:28
  • @user2693979 to put it simpler `new E[size]` is blocked by compiler because it wouldn't create `E[]` array (array of only E type) but `Object[]` array. – Pshemo Sep 02 '13 at 22:39
1

checked :

public Constructor(Class<E> c, int length) {

    elements = (E[]) Array.newInstance(c, length);
}

or unchecked :

public Constructor(int s) {
    elements = new Object[s];
}
Melih Altıntaş
  • 2,353
  • 15
  • 34
  • I am sorry , I forgot to explain he should change the elements type like that private Object[] elements; instead of using generic type. – Melih Altıntaş Sep 03 '13 at 09:03