32

I am playing with Generic and arrays, it seems the following code compiles fine,

ArrayList<Key> a = new ArrayList<Key>();

But the compiler complains about this one,

ArrayList<Key>[] a = new ArrayList<Key>[10];

By reading post in stackoverflow, I sort of understand that this is due to Type Erasure and I can fix it by using,

ArrayList<Key>[] a = (ArrayList<Key> []) new ArrayList[10];

or list of list

ArrayList<ArrayList<Key>> b = new ArrayList<ArrayList<Key>>();

But I can't figure out the reason behind the scene. Especially, why the second one is illegal given the first one is perfectly OK. And why the compiler does not complain about the list of list.

Hong
  • 393
  • 2
  • 6
  • 10
  • 3
    http://stackoverflow.com/questions/217065/cannot-create-an-array-of-linkedlists-in-java – tcb Oct 18 '11 at 15:59

5 Answers5

23

You can't have an array, because an array requires a raw type. You typecast it in the second instance, which makes it fit the defined type, and is therefore legal (however, this is impossible for it to infer). The list of list is legal as ArrayList isn't an array.

Read chapter 7.3 (page 15) in the official tutorial for more details on this.

The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type.You can declare array types whose element type is a type variable or a parameterized type, but not array objects. This is annoying, to be sure. This restriction is necessary to avoid situations like:

List<String>[] lsa = new List<String>[10]; // not really allowed
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // unsound, but passes run time store check
String s = lsa[1].get(0); // run-time error - ClassCastException

If arrays of parameterized type were allowed, the example above would compile without any unchecked warnings, and yet fail at run-time.

The tutorial then goes on to say the following:

Since type variables don’t exist at run time, there is no way to determine what the actual array type would be. The way to work around these kinds of limitations is to use class literals as run time type tokens

Chris Dennett
  • 21,465
  • 8
  • 52
  • 84
6

Array was poor man's generics; with real generics, one should avoid arrays, though not always possible.

Arrays are covariant, generics are invariant; combined with erasure, things just don't fit very well, as illustrated by the example in Chris's answer.

However I think it is possible to relax the spec to allow generic array creation - there's really no problem there. The danger comes when up casting the array; a compiler warning at that point is enough.

Actually Java does create generic arrays for vararg methods, so it's a little hypocritical.

Here are utility methods taking advantage of that fact

@SafeVarargs
static <E> E[] arrayLiteral(E... array)
{
    return array;
}

@SafeVarargs
static <E> E[] newArray(int length, E... array)
{
    return Arrays.copyOf(array, length);
}

// usage

    List<String>[] array1 = arrayLiteral(list, list);

    List<String>[] array2 = newArray(10);
irreputable
  • 42,827
  • 9
  • 59
  • 89
  • It's probably because of backward compatibility. Suppose some future version (Java 9?) permits generic array creation, then when Java 9 code mixes with Java 5/6/7/8 code (which can upcast arrays with no warning) the result will be unexpected ClassCastExceptions. – finnw Oct 18 '11 at 16:22
  • Note that in these code examples, the actual type of the array created is not `E[]`, but the erasure of some inferred type of `E`. For example, if you did `static T[] m() { T[] a = newArray(0); return a; }`, the actual type of `a` is always `Object[]`, because the inferred type of `E` is `T`, and the erasure of `T` is `Object`. If there's some code that calls that like `String[] a = m();`, it will throw a `ClassCastException`. – Radiodef Jul 01 '18 at 18:40
4

I had a similar question myself - FWIW, I didn't find the answers persuasive. The pertinent section from the most detailed answer (referring to the pdf reference) is this:

The component type of an array object may not be a type variable or a parameterized type, unless it is an (unbounded) wildcard type.You can declare array types whose element type is a type variable or a parameterized type, but not array objects. This is annoying, to be sure. This restriction is necessary to avoid situations like

       List<String>[] lsa = new List<String>[10]; // not really allowed
       Object o = lsa;
       Object[] oa = (Object[]) o;
       List<Integer> li = new ArrayList<Integer>();
       li.add(new Integer(3));
       oa[1] = li; // unsound, but passes run time store check
       String s = lsa[1].get(0); // run-time error - ClassCastException

So because I can cat the List[] to Object[], then shove something incorrect into the Object[], then refer to incorrectly from the List reference, through the casted ref, this is bad/disallowed? But only with new?

It's still more than a bit obscure to me how declaring this with new is any more or less of a problem than the usage, still crossing my eyes staring at it in the hope that it will start to make sense, or at least resolve into a nice 3d image.

Community
  • 1
  • 1
Steve B.
  • 49,740
  • 11
  • 90
  • 128
  • compiler needs to guarantee generic type safety; when it can't, it must either disallow the code or issue a warning. in the example, a warning at the 2nd line seems sufficient; java's decision to outright disallow generic array creation seems too harsh. – irreputable Oct 18 '11 at 16:17
  • 1
    Yes `ArrayList[] a = (ArrayList []) new ArrayList>[10]; ` still has the same problem, but it makes it clearer that you are cheating the type system (and it generates a warning.) – finnw Oct 18 '11 at 16:18
  • finnw, your comment is I think the right answer and the only way this makes sense to me. – Steve B. Oct 18 '11 at 16:34
2

Creating generic arrays isn't type-safe (see "Item 25: Prefer lists to arrays" of "Effective Java - second edition" by Joshua Bloch).

Use:

 List<List<Key>> b = new ArrayList<List<Key>>(10);

Or with Java SE 7:

 List<List<Key>> b = new ArrayList<>(10);
Puce
  • 34,375
  • 9
  • 72
  • 137
1

The arrays allow to escape type checks (as illustrated in the Chris's answer). So, you could have a code which passes all compiler checks (no "unchecked" warnings from compiler), but fail at run time with ClassCastException. Forbidding this construction raises the problem for a developer, so warnings do appear.

kan
  • 26,120
  • 6
  • 61
  • 96