What's the difference between A<:B and +B in Scala?
Q[A <: B]
means that class Q
can take any class A
that is a subclass of B
.
Q[+B]
means that Q
can take any class, but if A
is a subclass of B
, then Q[A]
is considered to be a subclass of Q[B]
.
Q[+A <: B]
means that class Q
can only take subclasses of B
as well as propagating the subclass relationship.
The first is useful when you want to do something generic, but you need to rely upon a certain set of methods in B
. For example, if you have an Output
class with a toFile
method, you could use that method in any class that could be passed into Q
.
The second is useful when you want to make collections that behave the same way as the original classes. If you take B
and you make a subclass A
, then you can pass A
in anywhere where B
is expected. But if you take a collection of B
, Q[B]
, is it true that you can always pass in Q[A]
instead? In general, no; there are cases when this would be the wrong thing to do. But you can say that this is the right thing to do by using +B
(covariance; Q
covaries--follows along with--B
's subclasses' inheritance relationship).
I would like to extend Rex Kerr's excellent answer with some more examples: Let's say we have four classes:
class Animal {}
class Dog extends Animal {}
class Car {}
class SportsCar extends Car {}
Let's start with variance:
case class List[+B](elements: B*) {} // simplification; covariance like in original List
val animals: List[Animal] = List( new Dog(), new Animal() )
val cars: List[Car] = List ( new Car(), new SportsCar() )
As you can see List does not care whether it contains Animals or Cars. The developers of List did not enforce that e.g. only Cars can go inside Lists.
Additionally:
case class Shelter(animals: List[Animal]) {}
val animalShelter: Shelter = Shelter( List(new Animal()): List[Animal] )
val dogShelter: Shelter = Shelter( List(new Dog()): List[Dog] )
If a function expects a List[Animal]
parameter you can also pass a List[Dog]
as an argument to the function instead. List[Dog]
is considered a subclass of List[Animal]
due to the covariance of List. It would not work if List was invariant.
Now onto type bounds:
case class Barn[A <: Animal](animals: A*) {}
val animalBarn: Barn[Animal] = Barn( new Dog(), new Animal() )
val carBarn = Barn( new SportsCar() )
/*
error: inferred type arguments [SportsCar] do not conform to method apply's type parameter bounds [A <: Animal]
val carBarn = Barn(new SportsCar())
^
*/
As you can see Barn is a collection only intended for Animals. No cars allowed in here.
for my Understanding:
The first is a parameter type bound, there a upper and lower typebounds in our case its a "type parameter A that is a subtype of B (or B itself).
The second is a Variance Annotation for a class defintion, in our case a covariance subclassing of B
Scala: + Java: ? extends T Covariant subclassing
Scala: - Java: ? super T Contravariant subclassing