20

Actually, the question should be

Creating an array of generic anything.

Why can't the compiler take care of it?

The following would be flagged as an error - cannot create generic array.

List<MyDTO>[] dtoLists = {new ArrayList<MyDTO>(), anExistingDtoList};

To overcome that, I need to

List<MyDTO>[] dtoLists = (List<MyDTO>[])Array.newInstance(ArrayList.class, 2);
dtoLists[0] = new ArrayList<MyDTO>();
dtoLists[1] = anExistingDtoList;

So, why can't the compiler convert the first case into the second case?

I do realise that generics are compile-time determinate and not run-time determinate, while arrays are run-time determinate and therefore need a determinate type in order to create an array.

What are the technological/logical barriers compiler designers would encounter that would prevent them being able to implement this?

Is the issue purely philosophical, concerning language orthogonality? If so, how would such a behaviour violate language orthogonality?

Is it a question of complexity? Explain the complexity.

I am hoping answers to my question would give me better insight into java compiler behaviour when it concerns generics.

Side note: c'mon stop being trigger happy. The answers Array of Generic List do not answer my question. Why can't compilers spontaneously perform the conversion?

Community
  • 1
  • 1
Blessed Geek
  • 19,240
  • 21
  • 96
  • 165

3 Answers3

8

Actually Java does create generic array for varargs, so you can do

List<MyDTO>[] dtoLists = array(new ArrayList<MyDTO>(), anExistingDtoList);

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

As to why is explicit generic array creation forbidden, it has something to do with type erasure. (The same concern exists in the above solution, but suppressed by @SafeVarargs) However it is debatable; there are different ways to handle the concern, a compiler warning is probably enough. But they chose to outright ban it, probably because arrays are no longer important anyway now that we have generic collections

irreputable
  • 42,827
  • 9
  • 59
  • 89
  • Arrays in Java are very useful for me, being a c++ programmer. In Java, I cannot pass pointers. I have to contain a variable in an object or in an array, to pass it around like a pointer. – Blessed Geek Dec 13 '11 at 03:39
  • I mean, before generics, we couldn't do `List`, so it's important to have `Foo[]` for static typing. That importance is gone. Of course arrays are still important as basic building block. – irreputable Dec 13 '11 at 03:52
3

I do know that, relative to the workarounds to this issue, Array.newInstance() is an expensive method to call. IIRC it uses a native method to instantiate the array, amidst the other reflection involved. I can't offer any statistics, but this seems like a good enough reason for such functionality not to be automatically substituted in by the compiler in order to allow generic array creation. Especially given the existence of ArrayList, etc. it just doesn't seem like a pressing issue.

Paul Bellora
  • 51,514
  • 17
  • 127
  • 176
1

Compilers can spontaneously perform the conversion, they are just specified not to because generic arrays can't behave like non-generic arrays.

See 10.5. Array Store Exception:

For an array whose type is A[], where A is a reference type, an assignment to a component of the array is checked at run time to ensure that the value being assigned is assignable to the component.

If the type of the value being assigned is not assignment-compatible with the component type, an ArrayStoreException is thrown.

If the component type of an array were not reifiable, the Java Virtual Machine could not perform the store check described in the preceding paragraph. This is why an array creation expression with a non-reifiable element type is forbidden.

A List<MyDTO>[] would not throw if we put some other kind of List in it, so it doesn't behave as an array. Note the last sentence from the quote: "This is why an array creation expression with a non-reifiable element type is forbidden." This is the reason, it's specified to be so. (And, for the record, this reasoning has always existed, so it was present when the question was posted in 2011.)

We can still do this:

@SuppressWarnings({"unchecked","rawtypes"})
List<MyDTO>[] dtoLists = new List[] {
    new ArrayList<MyDTO>(), anExistingDtoList
};

Or this:

@SuppressWarnings("unchecked")
List<MyDTO>[] dtoLists = (List<MyDTO>[]) new List<?>[] {
    new ArrayList<MyDTO>(), anExistingDtoList
};

(Besides statically checking the argument types, the varargs thing is equivalent: it creates a List[] and suppresses warnings.)

Now, sure, the specification could be changed to something like "If the type of the value being assigned is not assignment-compatible with the raw type of the component type...", but what is the point? It would save a handful of characters in some unusual situations but otherwise suppress warnings for those who don't understand the implications.

Furthermore, what the tutorial and other typical explanations I've seen don't demonstrate is just how baked in to the type system covariant arrays are.

For example, given the following declaration:

// (declaring our own because Arrays.fill is defined as
// void fill(Object[], Object)
// so the next examples would more obviously pass)
static <T> void fill(T[] arr, T elem) {
    Arrays.fill(arr, elem);
}

Did you know that this compiles?

// throws ArrayStoreException
fill(new String[1], new Integer(0));

And this compiles too:

// doesn't throw ArrayStoreException
fill(dtoLists, new ArrayList<Float>());

Before Java 8, we could make those calls to fill fail by giving it the following declaration:

static <T, U extends T> void fill(T[] arr, U elem) {...}

But that was only a problem with type inference, and now it works "correctly", blindly putting List<Float> in to a List<MyDTO>[].

This is called heap pollution. It can cause a ClassCastException to be thrown sometime later, likely somewhere completely unrelated to the actions that actually caused the problem. Heap pollution with a generic container like List requires more obvious unsafe actions, like using raw types, but here, we can cause heap pollution implicitly and without any warnings.

Generic arrays (and really, arrays in general) only give us static checking in the simplest of circumstances.

So it's evident that the language designers thought it was better to just not allow them, and programmers who understand the problems they present can suppress warnings and bypass the restriction.

Community
  • 1
  • 1
Radiodef
  • 35,285
  • 14
  • 78
  • 114