Applying Generics to Classes and Interfaces

You may want to support generics in your own classes and interfaces. This works a lot like how it does in Java, where you use T notation to indicate where you accept a varying type.

Use in Class Declaration

For example, you can create a class that can work with a particular type:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

data class Transport<T>(var passenger: T)

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

  println(critterCarrier.passenger::class)
}

Transport can handle an object of any type, but a particular instance of Transport can only handle objects of a single type (or subtypes, where applicable). When we pass a Frog instance into the Transport constructor, we lock the resulting Transport to transporting Frog objects. If we attempt to reassign passenger with another type:

critterCarrier.passenger = "This is not an Animal"

…we get a compile error:

error: type mismatch: inferred type is String but Test.Frog was expected
critterCarrier.passenger = "This is not an Animal"
                           ^

What the Transport can support will be based on the type of the variable or property handed to it. So, we can give a Transport a bit more flexibility by passing in an Animal, instead of a Frog, to it:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

data class Transport<T>(var passenger: T)

fun main() {
  val kermit: Animal = Frog()
  val critterCarrier = Transport(kermit)

  println(critterCarrier.passenger::class)

  critterCarrier.passenger = Axolotl()

  println(critterCarrier.passenger::class)
}

This works because now kermit is typed as an Animal, so critterCarrier is a Transport of Animal, so we can replace our Frog with an Axoltl if desired.

Use with Supertypes

We could also create a subtype that declares support for a specific type:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

interface Transport<T> {
  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)
}

Here, while Transport can handle any type, Van is an implementation of Transport that is restricted to transporting Animal objects.

Note that we can use the T type placeholder for properties, parameters, and — in the case of getPassenger() — return values.


Prev Table of Contents Next

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