I was messing around with creating a generic Bag collection in Java that’d be backed by an array. It turns out that you can’t do this for a number of interesting reasons…
In Java (and C#), arrays are covariant. This means that if Apple
is a subtype of Fruit
, then Apple[]
will also be a subtype of Fruit[]
. Pretty straightforward. That means this will compile:
Apple[] appleArray = new Apple[10]; appleArray[0] = new Apple(); Fruit[] fruitArray = appleArray; //Spot the problem?
If you’re like me, you didn’t think too hard about this, and assumed you could do the same with parameterized types, i.e. generics. Thanksfully you can’t, because that code is unsafe. It will throw an ArrayStoreException
at runtime which we’d have to handle.
Wouldn’t it be great if we could guarantee type safety at compile time?
Generics are safer
Unlike arrays, generics are invariant, which means that Apple
being a subtype of List<Apple>
 is different than a List<Fruit>
. The generic version of the code above is illegal:
Vector<Apple> apples = new Vector(); Vector<Fruit> fruits = apples; //Compile-time error
You can’t cast it, either:
Vector<Apple> apples = new Vector(); Vector<Fruit> fruits = (Vector<Fruit>)apples; //Still a compile-time error!
By making generics invariant, we guarantee safe behavior at compile time, which is a much cheaper place to catch errors. (This is one of the big reasons developers get excited about generics.)
So why are arrays and generics mutually exclusive?
In Java, generics have their types erased at compile time. This is called type erasure. Type erasure means a couple of things happen at compile time:
- Generic types are boiled down to their raw types: you cannot have a
Derp
and aDerp<T>
 in the same package. - A method that has a parameterized type overload won’t be compile: a class with methodsÂ
popFirst(Derp<T> derp)
 andpopFirst(Derp derp)
won’t compile. - Runtime casts are inserted invisibly by the compiler to ensure runtime type safety. (This means there’s no performance benefit to generics in Java!)
Java’s implementation of generic types is clumsy, and was done to maintain backward-compatibility in the bytecode between Java 5 and Java 4.
Other high-level languages (like C#) implement generics very differently, which means none of the three caveats above apply. Generics in full-stack implementations do net performance gains along with those type-safety guarantees.
To recap, in Java:
- Arrays require type information at compile time
- Generics have their types erased at compile time
Therefore you cannot create arrays of parameterized types in Java.