Co-Variance
Co-Variance is not new for Scala and Java developers. This already exists while we overriding a function in subclasses we can define covariance return type in the method signature as below:
In the above code, we are overriding the method bucket
and defining return type Apple
instead of Fruit
, which is called Co-Variance return type. The compiler allows us to define a subclass in the override definition because according to the polymorphism we can assign a subclass object to its supertype reference. Let’s take an example:
According to above example, it doesn’t matter shoppingCart.bucket(...)
return which Fruit
, because Fruit
is a superclass of all fruits according to the polymophism we can always catch any fruits objects(like Apple
, Orange
, etc) via Fruit
reference variable.
Note: We can use the co-variance return type in Java from version 5 onwards.
The way we are declaring the subclass, in override method signature is called co-variance return type, something similar we are performing in parameterized type co-variance. As shown in the Diagram 10.1, with the help of co-variance we can pass subclass parameterize type to superclass parameterize type. As per our example, we can pass or assign the object of Garage[Lamborghini]
(Garage of Lamborghini) or Garage[Jaguar]
(Garage of Jaguar) where it accepts Garage[Car]
(Garage of Car) method argument or a reference variable.
Again, remember one thing carefully, co-variance defines the relationship between Garage[Lamborghini]
(Garage of Lamborghini) with Garage[Car]
(Garage of Car) on the basis of passed type paramter. For creating a co-variance type we need to define one special syntax like below:
Note: plus symbol (+) is only allow ot use during define the types( like Class
, Traits
), you cannot use with variables and generics method.
In our example, we successfully created co-variance type Garage[+T]
which enables Garage[Car]
inheritance hierarchy, where we can pass any subtype of Garage[Car]
, and those are Garage[Lamborghini]
and Garage[Jaguar]
. Method carGarage(Garage[Car])
accepts subtype of Garage[Car]
objects.
Even, if some methods accept Garage[Lamborghini]
or Garage[Jaguar]
parameter, you can pass subclass of Garage[Lamborghini]
or Garage[Jaguar]
like Garage[LamborghiniUrus]
or Garage[JaguarXF]
, but you can’t pass supertype like Garage[Car]
to those methods because co-variance only accepts same or sub-hierarchy.
Co-Variance parameters have one important catch or you can say rule which we discuss below: While creating a generic type with the co-variance flag (+), the positions of the type parameter are only valid at methods return type location and variable datatype position as below:
Co-Variance FAQ
Why does co-variance allow declaring the type parameter at method return type position, not in method arguments position?
Before moving to the answer to this question, again go back and reminds our variance secrets.
When we creating an object of Garage[Car]
just assume something happens under the hood like below code:
When we creating an object of Garage[Lamborghini]
just assume something happens under the hood like below code:
something similar with Garage[Jaguar]
as well.
We have a method called carGarage(Garage[Car])
which accepts an object of Garage[Car]
type as an argument which is co-variance. When we pass the object of Garage[Lamborghini]
compiler checks, rule called Liskov Substitution Principle(LSP), where we can replace the object of supertype with subtype. Let's take an example:
Okay, let’s suppose this code is valid what are the chances we will face runtime exception? There are many, in method carGarage(Garage[Car], Car)
If someone pass Garage[Lamborgini]
type object and Jaguar
type object as parameters, there is the chance we will get a runtime exception because at runtime actual method is repairTheCar(Lamborgini)
and we are passing Jaguar
object, the first problem is ClassCaseException
, Second problem, suppose some internal conversion happens it converts Jaguar
to Lamborgini
but we lose the value of Lamborgini
features and there is a possibility we are using some those Lamborgini
particular features within the method repairTheCar(Lamborgini)
which are not available in converted Lamborgini
type which generates runtime exception:
This whole scenario violates the LSP where we do not completely replace subtype in the place of supertype in case of repairTheCar(car: T)
method call so that’s why the compiler does not allow us to define the type parameter at method argument level.
Why does co-variance allow type parameter at the location of variable datatype?
There is a simple answer because in Scala variables and methods are handled by the same namespace.
Is there any way to using the type parameter at the method argument location?
Yes, there is a way, but this is not as aspected. In chapter 9th we have an idea about type constraints where we are using Least Upper Bound and Greatest Lower Bound. For using type argument in co-varaince as a method parameter than Greatest Lower Bound comes into the picture. Let's take an example:
In method repairTheCar(car: U)
we defined Lower Bound means, the U type is greater than or equals to type T means U could be Any
type but not lower than passed T type parameter. Under the hood some thing happen as below:
When we create an object of Garage[Lamborghini]
type and passed to our method carGarage(Garage[Car], Car)
, everything is working fine.
The carGarage(lamborginiGarage, new Jaguar)
method call is compiled and run successfully because during the compilation, compilers know, repairTheCar(Any)
method accepts Any
type parameter if someone can pass any subtype of like Jaguar
with Garage[Lamborghini]
, there is no issue because repairTheCar(Any)
can only use Any
type features no there specific one, which are available in both Lamborghini
and Jaguar
objects.
Why does compiler not allow to pass supertype object in co-variance like `Garage[FourWheeler]` and generates compile time error?
For answering the question let’s take an example:
While we are passing Garage[FourWheeler]
to carGarage(Garage[Car], Car)
method, the compiler gives us an error because the compiler knows, Garage[Car]
maybe contains some method, which returns Car
type object, but in Garage[FourWheeler]
the same method must return FourWheeler
type object, as per hierarchy FourWheeler
is a superclass of Car
and as per the polymorphism it is not valid to catch the superclass object to subclass. Let's elaborate this via code:
While carGarage
method call washTheCar(car)
method as per implementation, it returns FourWheeler
type object, which is not possible to assing to the sub-class reference Car
type that's why in co-varaince super types are not allow.
Last updated