What's the Reason I Can't Create Generic Array Types in Java

What's the reason I can't create generic array types in Java?

It's because Java's arrays (unlike generics) contain, at runtime, information about its component type. So you must know the component type when you create the array. Since you don't know what T is at runtime, you can't create the array.

Why can't I create generic array in Java?

You can't create a generic array in Java. Arrays are reifiable types, and carry their type information at runtime whereas generics are non reifiable, and their type information is erased after the compile time due to erasure. This is due to the implementation of the type system in java and even though this causes some rough edges and corner cases, it eases the evolution of code into generics. Since generic type information is erased, you don't have them at runtime and that's why you can't create a generic array in java.

There are two solutions to circumvent the issue you are facing. You can either create an Object array and cast it to the generic type you need.

final T[] arr = (T[]) new Object[n]

or else

final Object[] arr = new Object[]

You can do the cast when you get the items from this array like this

T itm = (T) arr[1];

Both of the above approaches work if you don't return this internal array.

However, if you are returning the internal array, you need to create it reflectively, since you need to maintain the proper reifiable type.

static <T> T[] createArr(Class<T> clz) {
return (T[]) Array.newInstance(clz, 5);
}

Cannot create generic array of .. - how to create an Array of Map<String, Object>?

Because of how generics in Java work, you cannot directly create an array of a generic type (such as Map<String, Object>[]). Instead, you create an array of the raw type (Map[]) and cast it to Map<String, Object>[]. This will cause an unavoidable (but suppressible) compiler warning.

This should work for what you need:

Map<String, Object>[] myArray = (Map<String, Object>[]) new Map[10];

You may want to annotate the method this occurs in with @SuppressWarnings("unchecked"), to prevent the warning from being shown.

Cannot create a generic array of ... in Java

Java generics are not reified, which means that the 'implementations' are not classes in their own right. The fact that you can't create arrays of generic types is an inherent limitation of the language.

I'd recommend you to use a List instead of an array.

Cannot create a generic array

Problem here is that non-static nested class has access to all members of its outer classes, which includes information about generic types used in outer class, like

class Outer<T>{
private T t;
class Inner{
void method(T tt){//we can use same type T as used in outer class
t = tt;
}
}
}

So in reality Inner class type is more like Outer<T>.Inner which makes form of it generic type and arrays can't be created from generic types because of type erasure which would prevent arrays from being able to test if added elements are valid.

Most common solution in that cases is to use collections instead of arrays like List<OurType>.

But if you really want to have only arrays then other possible solution (but you should try to avoid it) is to use raw type, so instead of

new Entry[DEFAULT_CAPACITY];

which is equivalent of

new MapImpl<K, V>.Entry[DEFAULT_CAPACITY];

you could use

new MapImpl.Entry[DEFAULT_CAPACITY];
// ^no generic type -> it is raw type

Solution with

private class Entry<K, V> {
private K key;
private V value;
//but here K and V are being hidden.
public Entry(K key, V value) {
this.key = key;
this.value = value;
}
}

works probably (I can't find any relevant JLS describing this) because as you said, you have shadowed original K and V from outer class, which means you can't access them and now

        void method(T tt){
t = tt;
}

method will not compile because T from inner class is not the same as T from outer class. Because of that, Entry is no longer MapImpl<K,V>.Entry but MapImpl.Entry<K,V> and when you write it as

new Entry[...]

you are explicitly making it raw type which will work (with compilation warning about rawtypes when you declare private Entry[] entries)

How to create a generic array?

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.

How to create a generic array in Java?

I have to ask a question in return: is your GenSet "checked" or "unchecked"?
What does that mean?

  • Checked: strong typing. GenSet knows explicitly what type of objects it contains (i.e. its constructor was explicitly called with a Class<E> argument, and methods will throw an exception when they are passed arguments that are not of type E. See Collections.checkedCollection.

    -> in that case, you should write:

    public class GenSet<E> {

    private E[] a;

    public GenSet(Class<E> c, int s) {
    // Use Array native method to create array
    // of a type only known at run time
    @SuppressWarnings("unchecked")
    final E[] a = (E[]) Array.newInstance(c, s);
    this.a = a;
    }

    E get(int i) {
    return a[i];
    }
    }
  • Unchecked: weak typing. No type checking is actually done on any of the objects passed as argument.

    -> in that case, you should write

    public class GenSet<E> {

    private Object[] a;

    public GenSet(int s) {
    a = new Object[s];
    }

    E get(int i) {
    @SuppressWarnings("unchecked")
    final E e = (E) a[i];
    return e;
    }
    }

    Note that the component type of the array should be the erasure of the type parameter:

    public class GenSet<E extends Foo> { // E has an upper bound of Foo

    private Foo[] a; // E erases to Foo, so use Foo[]

    public GenSet(int s) {
    a = new Foo[s];
    }

    ...
    }

All of this results from a known, and deliberate, weakness of generics in Java: it was implemented using erasure, so "generic" classes don't know what type argument they were created with at run time, and therefore can not provide type-safety unless some explicit mechanism (type-checking) is implemented.

Why can't I create an array of a generic type?

The bottom line is that the class that represents the array has to know the component type. Hence the method on the Class object:

public Class<?> getComponentType()
Returns the Class representing the component type of an array. If this class does not represent an array class this method returns null.

So when you would try:

 A[] a = new A[0];

At compile time, it's obvious that we don't know the type, as it's a generic parameter. At runtime, we don't know the type due to type erasure. So instantiating the array is impossible.

Think of the above statement as equivalent to:

 A[] a = (A[])Array.newInstance(???, 0);

And due to type erasure, we can't get the class of A at runtime.

It was asked why not have the compiler reduce to Object[] or Number[] or something like that?

It's because a different Class will be returned depending on the component type. So:

 new Object[0].getClass() 
new Integer[0].getClass()

are not the same class. In particular the "getComponentType()" method on the class would return different values.

So if you reduce it to Object[] instead of A[], you're not actually getting back something of type A[], you're getting back Object[]. Object[] cannot be cased to Integer[] and will yield a ClassCastException.



Related Topics



Leave a reply



Submit