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
Apple will also be a subtype of
Fruit. Pretty straightforward. That means this will compile:
Apple appleArray = new Apple; appleArray = 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<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)Â and
popFirst(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.