Examining the type safety of the toArray() method of the Collection interface.

The method signature of toArray()

T is the method generic of toArray(T[] a) in the Collection<E> interface.
It is defined as so:

<T> T[] toArray(T[] a);

Testing it

import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
 
class Main {
  public static void main(String[] args) {
    List<Integer> c = new ArrayList<>();
    c.add(1);
    c.add(2);
    c.add(3);
 
    Number[] n = c.toArray(new Number[0]);
 
    String[] s = c.toArray(new String[0]);
  }
}

Runtime Exception

Exception in thread “main” java.lang.ArrayStoreException: arraycopy: element type mismatch: can not cast one of the elements of java.lang.Object[] to the type of the destination array, java.lang.String
at java.base/java.lang.System.arraycopy(Native Method)
at java.base/java.util.Arrays.copyOf(Arrays.java:3515)
at java.base/java.util.ArrayList.toArray(ArrayList.java:400)
at Main.main(Main.java:14)
[ Babel evaluation exited with code 1 ]

So, what is going on here?

Firstly, the code compiles! Even though logically it makes no sense, the compiler doesn’t know if ArrayList’s implementation of toArray() is able to return a String[]. All the compiler knows is that the return type of toArray() must match the argument’s type. Which it does.

But of course trying to get a String[] out of an List<Integer> doesn’t work and will lead to a runtime exception which is what we are seeing.

This begs the question. Since Collection<E> already knows the type of its elements via E, why doesn’t toArray() just use E??? Why does it allow us to write code that is type unsafe?? Doesn’t that defeat the purpose of the type system???

Why does toArray() use its own method generic?

The ArrayList and Collection interface already has a generic type E for specifiying the type of the Elements it contains. So why doesn’t toArray() just use the same type?

This is so that the array that is created is of the exact type you want.

What does that mean and why is that important? To explain this, we need to look at some code.

import java.util.ArrayList;
import java.util.List;
 
class main {
  public static void main(String[] args) {
    List<Integer> intList = new ArrayList<>();
    intList.add(123);
    Number[] a = intList.toArray(new Number[0]);
    Number[] b = intList.toArray(new Integer[0]);
 
    a[0] = 1.337f;
    b[0] = 1.337f; // Line 12. Runtime error happens here.
  }
}

Runtime Exception

Exception in thread “main” java.lang.ArrayStoreException: java.lang.Float
at main.main(main.java:12)
[ Babel evaluation exited with code 1 ]

Soooo what is going on there…? Assigning a float to a works because the array that is created is of the type Number, which accepts all subtypes of Number.

Assigning a Integer[] to a Number[] works because Arrays in Java are Covariant! Assigning a float to b fails at runtime because the underlying type of b is still Integer[].

That’s p evil behaviour! So that is the reason why toArray() uses its own generic instead of following E! It allows for a full conversion of a Collection<T> from its current type to any parent type! …or realistically ANY type, if the concrete implementation allows.