Exercise 1: Covariance and Contravariance
To better understand covariance and contravariance, we'll start by creating an easy to understand hierarch of types:open class Animal open class Dog: Animal() class Chihuahua: Dog() class Poodle: Dog() open class Cat: Animal() class Persian: Cat() class Birman: Cat()So in this structure, the top level is Animal. A Dog is an Animal, and a Cat is an Animal. A Chihuahua is a Dog (which in turn is an Animal), and same goes for a Poodle. Persian and Birman are both Cats.
With this type hierarchy, we can create lists that can contain different categories of animals:
val animals = mutableListOf<Animal>() val dogs = mutableListOf<Dog>() val cats = mutableListOf<Cat>() val chihuahuas = mutableListOf<Chihuahua>() val poodles = mutableListOf<Poodle>() val persians = mutableListOf<Persian>() val birmans = mutableListOf<Birman>()Now what we want to do is create a function called addDogs that will take in two lists. The first list should be any list that any kind of dog can be added to (either a list of animals or a list of dogs would fit the bill). The second list should be a list that is guaranteed to only have dogs in it (in this case, either a list of dogs, a list of chihuahuas, or a list of poodles would work). This function will take all the dogs in the second list and add them to the first list. Such a function would look like this in Java:
public void addDogs(List<? super Dog> list, List<? extends Dog> toAdd) { list.addAll(toAdd); }This would in turn allow for the following legal uses:
addDogs(animals, dogs) addDogs(dogs, otherDogs) addDogs(animals, chihuahuas) addDogs(dogs, chihuahuas) addDogs(animals, poodles) addDogs(dogs, poodles)While the following scenarios won't compile:
addDogs(chihuahuas, dogs) //chihuahuas can't accept any kind of dog addDogs(dogs, animals) //animals might contain cats addDogs(cats, persians) //while cats can hold persians, neither fullfill the contract, //cats can't accept dogs, and persians doesn't contain dogsSo can we write the addDogs method in Kotlin? As a bonus activity, we can also explore converting it into an extension function.
Exercise 2: Reified Types
For this exercise, we'll use the RxJava2 library, so you'll want to add the following to your gradle file:compile group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.11'We'll also make use of the same animal classes from the above example, but we'll add a bark method to the Dog class, for illustrative purposes:
open class Dog: Animal() { fun bark() { println("bark") } }Now RxJava has a cast operator so that you can cast an object from one type to another in its chain. For instance, if you have an animal that you know is a Poodle and you need to cast it to Dog so that you can all the bark function on it:
val animal = Poodle() as Animal Observable.just(animal) .cast(Dog::class.java) .subscribe{ dog -> dog.bark() }Now the fact that the cast method takes in a Java class parameter makes it kind of long and ugly. Let's see if we can write an extension function that would allow us to do this instead:
val animal = Poodle() as Animal Observable.just(animal) .cast<Dog>() .subscribe{ dog -> dog.bark() }This will require us to make use of reified types.
As always, the answers will be given in a follow up post. And here's the answers.