Service Symphony

Variance annotations in Java and Scala

Java generics are invariant. This means, for example, a List<Integer> is not a sub type of List<Number>, even though Integer is a sub type of number. This means, the following code won’t compile.

List<Number> list = new ArrayList<Integer>()

If the compiler allowed this, you could pollute the heap by adding a type other than integer to the list as follows,

list.add(3.14)

So by making generics invariant, you can stop runtime heap corruption. However, this comes at the cost of polymorphic use of generics. This means you can’t, for example, have a method that accepts List<Number> to which you can pass List<Integer> or List<Float>. To make it work, one needs to make the declaration covariant as shown below,

List<? extends Number> list = new ArrayList<Integer>()
Number number = list.get(0);

Now since any member of the above list is a sub type of Number, one can safely assign the individual elements to of type Number. However, if you try to write to the list, the compiler again complains, because of the same issue of preventing heap corruption. If you need to do this, you need to make the list contra-variant,

List<? super Number> list = new ArrayList<Integer>();
list.add(12);
list.add(3.14);

This now compiles, because the list can accomodate anything to which Number is a super type. So in general, reads need to be co-variant and write needs to be contra-variant.

The key words extends and super, here are variance annotations. In Java variance annotations are used at use site, this means once the parameterized class has been wrtitten, all these issues are encountered when one tries to use the class.

Scala’s approach is different. It uses definition site variance annotations. By default Scala generics are invariant as well.

class List[T]
val v : List[AnyVal] = new List[Int]

The above won’t compile because List[Int] is not a sub type of List[AnyVal]. However, to make the above work, you can make the List covariant by

class List[+T]

Similarly to make the type contra=variant, one would write

class List[-T]

This will allow us to do the below,

val v : List[Int] = new List[AnyVal]

You may have types that mix both co-variance as well as contra-variance, an example below is what Scala internally uses for function literals,

trait Function[-S, +T) {
  def apply(s : S) : T
}

This makes perfect sense as the parameter, which is what written into is contra variant and result which is read from is co-variant. By using definition site variance annotations, Scala allows better rigor in type design than Java.

Share this:

Leave a Reply

Your email address will not be published. Required fields are marked *

nine − 1 =

Some of our
Clients
Service Symphony

Service symphony © 2024

Quick Links
Contact Info

20-22 Wenlock Road
London
N1 7GU
United Kingdom

Support

0800 011 4386