Covariant containers
If there is a subtyping between two types, say that every is a , then it is natural to extend this subtyping to container types, to say that a is also a .
However, this is only sound for immutable types. If is mutable, then a is something into which I can insert a . If every is automatically a , then you can end up with buses in your list of cars.
This problem occurs with arrays in Java:
class Vehicle {}
class Car extends Vehicle {}
class Bus extends Vehicle {}
public class App {
public static void main(String[] args) {
Car[] c = { new Car() };
Vehicle[] v = c;
v[0] = new Bus(); // crashes with ArrayStoreException
}
}
The solution is to keep track of variance (how subtyping of type parameters affects subtyping of the whole type). There are two approaches:
-
Use-site variance is used in Java (for types other than arrays): a
List<Car>
can never be converted to aList<Vehicle>
, but can be converted to aList<? extends Vehicle>
. The elements of aList<? extends Vehicle>
are known to beVehicle
s, but an arbitraryVehicle
cannot be inserted into such a list. Each use ofList
specifies how the parameter is allowed to vary. -
Declaration-site variance is used in Scala (although use-site variance is also available). This means that the
List
type can be defined asList[+T]
if immutable, making everyList[Car]
automatically aList[Vehicle]
. However, ifList
is mutable, it must be defined asList[T]
, which disables these conversions.