Cover by Alex Payne, Dean Wampler

Safari, the world’s most comprehensive technology and business learning platform.

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required

O'Reilly logo

Variance Under Inheritance

An important difference between Java and Scala generics is how variance under inheritance works. For example, if a method has an argument of type List[AnyRef], can you pass a List[String] value? In other words, should a List[String] be considered a subtype of List[AnyRef]? If so, this kind of variance is called covariance, because the supertype-subtype relationship of the container (the parameterized type) “goes in the same direction” as the relationship between the type parameters. In other contexts, you might want contravariant or invariant behavior, which we’ll describe shortly.

In Scala, the variance behavior is defined at the declaration site using variance annotations: +, -, or nothing. In other words, the type designer decides how the type should vary under inheritance.

Let’s examine the three kinds of variance, summarized in Table 12-1, and understand how to use them effectively. We’ll assume that Tsup is a supertype of T and Tsub is a subtype of T.

Table 12-1. Type variance annotations and their meanings

AnnotationJava equivalentDescription

+

? extends T

Covariant subclassing. E.g., List[Tsub] is a subtype of List[T].

-

? super T

Contravariant subclassing. E.g., X[Tsup] is a subtype of X[T].

none

T

Invariant subclassing. E.g., Can’t substitute Y[Tsup] or Y[Tsub] for Y[T].

The “Java equivalent” column is a bit misleading; we’ll explain why in a moment.

Class List is declared List[+A], which means that List[String] is a subclass of List[AnyRef], so Lists are covariant ...

Find the exact information you need to solve a problem on the fly, or go deeper to master the technologies and skills you need to succeed

Start Free Trial

No credit card required