Covariant Subtyping for Arrays
Java's type system includes the following general rules:
If an expression E is of type
T1
, andT1
is a subtype ofT2
, then E is of typeT2
.If an expression E is of type
T1
, and a variable x is of typeT2
, andT1
is a subtype ofT2
, then the assignmentx=E
is well-typed (that is, legal).If
T1
is a subtype ofT2
, thenT1[]
is a subtype ofT2[]
.
The last of those rules is the covariant subtyping rule for
Java's array type constructor.
(It's covariant rather than contravariant because the
subtyping relationship between
T1[]
and
T2[]
runs in the same direction as the subtyping relationship between
T1
and
T2
.)
Covariant subtyping for the array type constructor improves reuse
and makes programming easier because you can pass arguments of
type T1[]
to a method m
that accepts
arguments of type T2[]
instead of having
to write a second version of m
that accepts arguments
of type T1[]
.
Unfortunately, covariant subtyping of the array type constructor
is unsound when arrays are mutable.
Suppose T1
is a subclass of
T2
and defines a method g
that isn't defined by class T2
.
Then the following Java code will pass the type checker
and compile just fine, but throws an exception at run time:
void f (T2[] a) { T1 x = new T2(); for (int i = 0; i < a.length; i = i + 1) a[i] = x; } void troublesome () { T1[] a = new T1[1000]; f(a); System.out.println("Something is about to go wrong!"); a[0].g(); }
To detect that run-time error and throw a clean exception, implementations of Java must implement a mechanism that checks every assignment that stores into an array of reference values.
If you modify the example above to use ArrayList
s
instead of arrays, the Java compiler will reject the program.
The parametric polymorphism of ArrayList
is not
covariant.