Generic Arrays in Java

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.

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.

Generic arrays in Java

Generics and arrays don't mix, basically. The short answer is that you can work around this problem. The longer answer is that you probably shouldn't and I'll explain why.

You could use Array.newInstance() like this:

private Comparable[] hashtable;

...

hashtable = (Comparable[])Array.newInstance(Comparable.class, tableSize);

but you can't create an array of your parameterized type.

Arrays are covariant. That means they retain the type of their elements at runtime. Java's generics are not. They use type erasure to basically mask the implicit casting that is going on. It's important to understand that.

So when you create an Object array you can't cast it to, say, a Comparable array (or any other type) because that is not correct.

To give you an example. With generics this is perfectly legal:

List<String> list = new ArrayList<String>();
List<Integer> list2 = (List<Integer>)list;
list.add(3);

It's also why you can't do this:

public <T> T newInstance(T t) {
return new T(); // error!
}

ie at runtime there is no knowledge of T's class. This is why the above code is more often written as:

public <T> T newInstance(T t, Class<T> clazz) {
return clazz.newInstance();
}

because their is no runtime type for the generic argument. But with arrays:

String arr[] = new String[10];
Integer arr2[] = (Integer[])arr; // error!

What you should be doing in this case (imho) is not using arrays but using an ArrayList. In all honesty, there is very little reason to use arrays over an ArrayList and generics is just one example of that.

For a better and more complete explanation see the (excellent) Java Generics FAQ:

Can I create an array whose component type is a concrete parameterized type?


No, because it is not type-safe.

Arrays are covariant, which means that
an array of supertype references is a
supertype of an array of subtype
references. That is, Object[] is a
supertype of String[] and a string
array can be accessed through a
reference variable of type Object[].

...

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.

Java Generic array creation allowed?

The code is allowed because new jj[10] is not a generic array (e.g. new T[10]), but is instead an array of raw jj instances - jj with its generic arguments omitted. new jj[10] creates a new array of raw jj, not a new array of jj<Integer>. Java allows one to force instances of a raw type to be assigned to a reference of type with generic arguments as the code does with jj<Integer>[] lol = new jj[10];. I believe this is there for backwards compatibility with pre-Java 5 code.

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);
}

How to make a generic method that merges arrays of any type?

You can't create an array of generic type in Java, but you can use a creator lambda:

public static<T extends Comparable<T>> T[] mergeAndSortArrays(
T[] a,
T[] b,
IntFunction<T[]> arrayCreator
) {
final T[] merged = arrayCreator.apply(a.length + b.length);

...
}

and then:

mergeAndSortArrays(i1, i2, Integer[]::new)
mergeAndSortArrays(s1, s2, String[]::new)

Or alternatively, you have Class<T> itemClass as method parameter (instead of the lambda) and then use (T[]) Array.newInstance(itemClass, a.length + b.length) to create new array.

If you want to avoid additional method arguments (like creator lambda or item class), you can use reflection to retrieve array item type:

final Class<?> itemClass = a.getClass().getComponentType();
final T[] merged = (T[]) Array.newInstance(itemClass, a.length + b.length);

And of course you can also use an existing library for this, like Apache Commons:

final T[] merged = org.apache.commons.lang3.ArrayUtils.addAll(a, b);
// ...

Generic array cast exception

Works as designed: at runtime there is no generic type.

It gets erased, and an array of Object is created. An array of Object can not be cast to another kind of array.

This is one of the restrictions of Java generics, based on the way how they are implemented.

That is way you are advised to be careful using arrays together with generics. You might prefer to use a generic List instead.

And just to be clear about this: yes, you can get arrays to work with generics, as elegantly shown by the other answers. But: you spend your time fighting symptoms doing so. Arrays and generics don't go together nicely in Java. Accept that, use generic lists and thus: fix the problem instead of working around it.

Java Generics: Array Casting

The reason the cast does not work is that type erasure of Node<E> applies to E inside Node, but it does not apply to arrays of Node<E>.

Type erasure would let you cast Node<E> to Node<Object> because all Node<E>s are Node<Object> under the hood. Similarly, it would let you cast Node<E>[] to Node<Object>[] and back, because the arrays share the same underlying type - that is, Node<Object>.

However, type erasure is not in play when you deal with arrays. An array of objects remains an array of objects, and Java has enough metadata to know it.

You have several ways of solving this problem:

  • Switch from Node<E>[] to ArrayList<Node<E>> - array list has similar footprint and access times, and is generic
  • Use a solution from this Q&A - type-safe solution requires passing Class<E>.

Generic Array initialisation

After erasure, the getStringArr should return Object[], and it is able
to cast to String[] without any problem.

Return type of the getStrArr, after type erasure, would be Object[] but, in your code, it is returning arr which is of type String[]. That is why there is not ClassCastException in your code.

Consider the following method (suppose generic arrays were allowed):

public T[] foo() {
return new T[5];
}

After type erasure, new T[5] will be replaced by new Object[5]. Now if the calling code calls this method as:

String[] strArr = obj.foo();

It will lead to ClassCastException because Object[] cannot be casted to String[].



Related Topics



Leave a reply



Submit