by Rahel Lüthy
While there are tons of articles explaining the concept of covariance in Java, this post attempts to highlight how and why Java and Scala differ when it comes to collection type safety.
Let’s start with Java arrays. They are covariant, meaning that an array of type
S is a subtype of an array of type
S is a subtype of
Things are simple and life is good. The fact that arrays are covariant makes code re-use possible. Utility methods inside
java.util.Arrays are perfect examples, e.g.:
As usual, everything falls apart because of mutability. Java arrays are mutable, making it possible to store the wrong type of object into an array without the compiler being able to tell:
Ouch, heap pollution – an
ArrayStoreException is thrown at runtime! People obsessed with types tend to call this unsound.
With Java 5 came generics, which allow “a type or method to operate on objects of various types while providing compile-time type safety.” (Oracle Java 1.5 Docs). Let me repeat that: Generics were introduced to improve type safety. It was thus not an option to accept the same unsound array behavior. Because Java collections are mutable, the only option was to make a
Cat is a subtype of
List<Cat> is not a subtype of
List<Animal>. This is not very intuitive, but it is necessary to prevent heap pollution at runtime.
Obviously, we still want to write re-usable utility code. Wildcards are making that possible. Methods inside
java.util.Collections are good examples, e.g.:
In Java, you thus get better compile-time safety when using generified collections than when using raw arrays.
In Scala, things are a lot different.
Array[T] is Scala’s representation for Java’s
T. Like in Java, a Scala
Array is mutable. But in contrast to Java, Scala cares a lot more about compile-time type safety. The only way to prevent unsound heap pollution is to make
Scala arrays are thus safer to use than Java arrays.
Things are even better for Scala
List, which is immutable. It is thus perfectly safe to make
List[+T] covariant (notice the little
+). There’s just no way to ever sneak in an object, let alone sneaking in an object with the wrong type:
In other words: In Scala,
List[S] is a subtype of
S is a subtype of