Chapter 9 covers generics, including topics such as invariance, covariance, contravariance, inlining, and reified types. We'll go over two different exercises to help us better understand some of these points in Kotlin.
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 dogs
So 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.