Wednesday, August 16, 2017

Covariance & Contravariance in Java

Covariance

If the generic type's subtype relationship is the same as the type parameter T, then the generic type is covariant w.r.t type parameter T.

A is a subtype of B => Generic<A> is a subtype of Generic<B>

In order to define a generic type as covariant, the extends keyword is used which defines the upper bound.

Example:

public class Animal {}
public class Mammal extends Animal {}
public class Cat extends Mammal {}
public class Dog extends Mammal {}


List<Animal> animals = ...
List<Mammal> mammals = ...
List<Cat> cats = ...
List<Dog> dogs = ...

public void someFunc(List<? extends Mammal> mammals)
                                |
                                V
               Here 'extends' defines the upper-bound as Mammal

Covariants are producers 

New items cannot be added to covariants after their construction.
public void someFunc(List<? extends Mammal> mammals) {
    mammals.add(new Cat());  ==> Does not compile
    mammals.add(new Mammal()); ==> Does not compile
}
   
However, one can access the items from covariant types, like so...

public void someFunc(List<? extends Mammal> mammals) {
    Mammal m = mammals.get(0); ==> Compiles
}

Also, someFunc() can only accept List<Mammal> or it's subtypes. 

someFunc(cats); ==> Compiles
someFunc(mammals); ==> Compiles
someFunc(animals); => Does NOT compile


Contravariance

If the generic type's subtype relationship is the opposite of type parameter T, then the generic type is contravariant w.r.t type parameter T.

A is a subtype of B => Generic<B> is a subtype of Generic<A>

In order to define a generic type as contravariant, the super keyword is used which defines the lower bound.

Example:

public void someFunc(List<? super Mammal> animals)
                                |
                                V
               Here 'super' defines the lower-bound as Mammal

Contravariants are consumers 

New items can be added to contravariants after their construction.
public void someFunc(List<? super Mammal> animals) {
    animals.add(new Cat());  ==> Compiles 
                             ==> Adding a Cat compiles as the 
                                 lower bound does NOT apply to the 
                                 constituent type <?> but only to                                    the generic type List<?>
    animals.add(new Mammal()); ==> Compiles
}

However, one cannot access the items from contravariant types, like so...

public void someFunc(List<? super Mammal> animals) {
    Animal a = animals.get(0); ==> Does not compile
}

Also, someFunc() can only accept List<Mammal> or it's super types. 

someFunc(cats); => Does NOT compile
someFunc(dogs); => Does NOT compile
someFunc(mammals); ==> Compiles
someFunc(animals); ==> Compiles
someFunc(objects); ==> Compiles

No comments: