Upper Bounds

A simple declaration of T could be any type. Sometimes, we need to limit it to only be certain types. The simplest way to do that is to declare T a bit like a class or interface, using a colon and a type to declare what T must inherit from:

open class Thingy

open class Animal : Thingy()

class Frog : Animal()

class Axolotl : Animal()

interface Transport<T : Thingy> {
  abstract fun getPassenger(): T
}

data class Van(val animal: Animal) : Transport<Animal> {
  override fun getPassenger() = animal
}

fun main() {
  val kermit = Frog()
  val critterCarrier = Van(kermit)

  println(critterCarrier.getPassenger()::class)
}

Now Transport cannot transport anything — it is limited to instances of Thingy. Since an Animal is a Thingy, we can still transport animals. But we cannot create a Transport of String, because a String does not extend from Thingy. If we try anyway:

open class Thingy

interface Transport<T : Thingy> {
  abstract fun getPassenger(): T
}

data class StringVehicle(val value: String) : Transport<String> {
  override fun getPassenger() = value
}

…we wind up with a compile error:

error: type argument is not within its bounds: should be subtype of 'Test.Thingy'
data class StringVehicle(val value: String) : Transport<String> {
                                                        ^

Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.