Chapters

Hide chapters

Kotlin Apprentice

Second Edition · Android 10 · Kotlin 1.3 · IDEA

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Building Your Own Types

Section 3: 8 chapters
Show chapters Hide chapters

Section IV: Intermediate Topics

Section 4: 9 chapters
Show chapters Hide chapters

18. Generics
Written by Ellen Shapiro

Heads up... You’re accessing parts of this content for free, with some sections shown as bqjilvhil text.

Heads up... You’re accessing parts of this content for free, with some sections shown as jtcekzkah text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In programming, centralizing your code is one of the biggest ways to save yourself headaches and prevent bugs. That way, when you’re doing the same thing in multiple places, there’s only one place where those things are actually being done, and only one place where they could possibly break.

A really helpful feature of Kotlin for this is called generics. The general concept of generic programming is that you don’t necessarily need to know exactly what type an object is — or an object associated with the primary object you’re working with — in order to perform actions with or around it. This allows you to combine and simplify functionality in really, really powerful ways.

Anatomy of standard library generic types

When getting started with generics, it helps to look at the major generic types that are included in Kotlin’s standard library. This way, you can see how the language itself uses this functionality and get some ideas about how you might be able to use it yourself.

Lists

You’ve probably noticed working with List objects that you sometimes need to declare them with the type of item you expect in the list in angle brackets, such as List<String>, or List<Int>.

Heads up... You’re accessing parts of this content for free, with some sections shown as mglulztal text.

Heads up... You’re accessing parts of this content for free, with some sections shown as bwfofqhem text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
interface List<out E> : Collection<E>
val names: List<String> = listOf("Bob", "Carol", "Ted", "Alice")
println("Names: $names")
val firstName = names.first()
fun <T> List<T>.first(): T

Heads up... You’re accessing parts of this content for free, with some sections shown as spwirkhej text.

Heads up... You’re accessing parts of this content for free, with some sections shown as mmzunkmoc text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
println(firstName)

Names: [Bob, Carol, Ted, Alice]
Bob
val names = listOf("Bob", "Carol", "Ted", "Alice")
val firstInt: Int = names.first()

Heads up... You’re accessing parts of this content for free, with some sections shown as nmbuqrhan text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wlbopldyn text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

val things = mutableListOf(1, 2)
things.add("Steve")
println("Things: $things")

Heads up... You’re accessing parts of this content for free, with some sections shown as crsyddgem text.

Heads up... You’re accessing parts of this content for free, with some sections shown as hbturmmap text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
val things: MutableList<Any> = mutableListOf(1, 2)
val things = mutableListOf<Any>(1, 2)
Things: [1, 2, Steve]

Maps

Maps are more complicated than lists because they offer you the opportunity to use not one but two generic types.

interface Map<K, out V>
val map = mapOf(
  Pair("one", 1),
  Pair("two", "II"),
  Pair("three", 3.0f)
)
val one = map.get(1)

Heads up... You’re accessing parts of this content for free, with some sections shown as cmdyrxvut text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wsxedbvop text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

val one = map[1]

val valuesForKeysWithE = map.keys
    .filter { it.contains("e") }
    .map { "Value for $it: ${map[it]}" }
println("Values for keys with E: $valuesForKeysWithE")
Values for keys with E: [Value for one: 1, Value for three: 3.0]

Heads up... You’re accessing parts of this content for free, with some sections shown as zvvucclyx text.

Heads up... You’re accessing parts of this content for free, with some sections shown as vhvumlwep text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Extension functions on types with generic constraints

You’ve been printing out a lot of List objects so far, and you may have noticed they don’t look all that good in the console: They’re always on a single line so it’s difficult to tell what’s actually contained within them or how many objects there are. Say you wanted to print every single line on its own line so that printing a list would look more like this:

- First Item
- Second item
- Third Item
fun List<String>.toBulletedList(): String {
  val separator = "\n - "
  return this.map { "$it" }.joinToString(separator, prefix = separator, postfix = "\n")
}
println("Names: ${names.toBulletedList()}")
println("Values for keys with E: ${valuesForKeysWithE.toBulletedList()}")
Names:
 - Bob
 - Carol
 - Ted
 - Alice

Bob
Things: [1, 2, Steve]
Values for keys with E:
 - Value for one: 1
 - Value for three: 3.0
println("Things: ${things.toBulletedList()}")

Heads up... You’re accessing parts of this content for free, with some sections shown as bqbubldoz text.

Heads up... You’re accessing parts of this content for free, with some sections shown as cxcovthit text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

fun List<Any>.toBulletedList(): String {
}

fun List<T>.toBulletedList(): String {

fun <T> List<T>.toBulletedList(): String {

Heads up... You’re accessing parts of this content for free, with some sections shown as tgpomzpaj text.

Heads up... You’re accessing parts of this content for free, with some sections shown as wxnorthyt text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Things:
 - 1
 - 2
 - Steve

Creating your own generic constraints

Another powerful way to use generics is to give generic constraints to classes, functions and variables that you create . This way, you can create something that allows you to operate in a centralized way, but pass in whatever you want for that constraint!

// 1
class Mover<T>(
    // 2
    thingsToMove: List<T>,
    val truckHeightInInches: Int = (12 * 12)
) {

  // 3
  private var thingsLeftInOldPlace = mutableListOf<T>()
  private var thingsInTruck = mutableListOf<T>()
  private var thingsInNewPlace = mutableListOf<T>()

  // 4
  init {
    thingsLeftInOldPlace.addAll(thingsToMove)
  }

  // 5
  fun moveEverythingToTruck() {
    while (thingsLeftInOldPlace.count() > 0) {
      val item = thingsLeftInOldPlace.removeAt(0)
      thingsInTruck.add(item)
      println("Moved your $item to the truck!")
    }
  }

  // 6
  fun moveEverythingIntoNewPlace() {
    while (thingsInTruck.count() > 0) {
      val item = thingsInTruck.removeAt(0)
	  thingsInNewPlace.add(item)
      println("Moved your $item into your new place!")
    }
  }

  // 7
  fun finishMove() {
    println("OK, we finished! We were able to move your:${thingsInNewPlace.toBulletedList()}")
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as wnrakppov text.

Heads up... You’re accessing parts of this content for free, with some sections shown as kkkakfvog text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
class CheapThing(val name: String) {
  override fun toString(): String {
    return name
  }
}
val cheapThings = listOf(
    CheapThing("Cinder Block table"),
    CheapThing("Box of old books"),
    CheapThing("Ugly old couch")
)
val cheapMover = Mover(cheapThings)
cheapMover.moveEverythingToTruck()  
cheapMover.moveEverythingIntoNewPlace()
cheapMover.finishMove()
Moved your Cinder Block table to the truck!
Moved your Box of old books to the truck!
Moved your Ugly old couch to the truck!
Moved your Cinder Block table into your new place!
Moved your Box of old books into your new place!
Moved your Ugly old couch into your new place!
OK, we finished! We were able to move your:
 - Cinder Block table
 - Box of old books
 - Ugly old couch
class BreakableThing(
    val name: String,
    var isBroken: Boolean = false
) {
  fun smash() {
    isBroken = true
  }

  override fun toString(): String {
    return name
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as xcmyzvsyt text.

Heads up... You’re accessing parts of this content for free, with some sections shown as lbgimrxex text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
val television = BreakableThing("Flat-Screen Television")
val breakableThings = listOf(
      television,
      BreakableThing("Mirror"),
      BreakableThing("Guitar")
  )
val expensiveMover = Mover(breakableThings)
expensiveMover.moveEverythingToTruck()
expensiveMover.moveEverythingIntoNewPlace()
expensiveMover.finishMove()
Moved your Flat-Screen Television to the truck!
Moved your Mirror to the truck!
Moved your Guitar to the truck!
Moved your Flat-Screen Television into your new place!
Moved your Mirror into your new place!
Moved your Guitar into your new place!
OK, we finished! We were able to move your:
 - Flat-Screen Television
 - Mirror
 - Guitar
television.smash()
fun moveEverythingToTruck() {
  while (thingsLeftInOldPlace.count() > 0) {
    val item = thingsLeftInOldPlace.removeAt(0)

    if (item is BreakableThing) {
      if (!item.isBroken) {
        thingsInTruck.add(item)
        println("Moved your $item to the truck!")
      } else {
        println("Could not move your $item to the truck")
      }
    } else {
      thingsInTruck.add(item)
      println("Moved your $item to the truck!")
    }
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as ccrykmfef text.

Heads up... You’re accessing parts of this content for free, with some sections shown as qwgojgpyr text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Interfaces

Interfaces allow you to declare information about what something does, rather than what it is, as a class hierarchy would.

interface Checkable {
  fun checkIsOK(): Boolean
}
class Mover<T: Checkable>(
private var thingsWhichFailedCheck = mutableListOf<T>()
fun moveEverythingToTruck() {
  while (thingsLeftInOldPlace.count() > 0) {
    val item = thingsLeftInOldPlace.removeAt(0)

    if (item.checkIsOK()) {
      thingsInTruck.add(item)
      println("Moved your $item to the truck!")
    } else {
      thingsWhichFailedCheck.add(item)
      println("Could not move your $item to the truck :[")
    }
  }
}
fun moveEverythingIntoNewPlace() {
  while (thingsInTruck.count() > 0) {
    val item = thingsInTruck.removeAt(0)
    if (item.checkIsOK()) {
	  thingsInNewPlace.add(item)
	  println("Moved your $item into your new place!")
	} else {
	  thingsWhichFailedCheck.add(item)
	  println("Could not move your $item into your new place :[")
    }
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as dbruzdxyl text.

Heads up... You’re accessing parts of this content for free, with some sections shown as zhfudwnez text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
fun finishMove() {
  println("OK, we finished! We were able to move your:${thingsInNewPlace.toBulletedList()}")
  if (thingsWhichFailedCheck.isNotEmpty()) {
	println("But we need to talk about your:${thingsWhichFailedCheck.toBulletedList()}")
  }
}

class CheapThing(val name: String): Checkable {

override fun checkIsOK(): Boolean = true
class BreakableThing(
    val name: String,
    var isBroken: Boolean = false
): Checkable {

Heads up... You’re accessing parts of this content for free, with some sections shown as hszistzeb text.

Heads up... You’re accessing parts of this content for free, with some sections shown as bxnivlkun text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
override fun checkIsOK(): Boolean {
  return !isBroken
}
Moved your Flat-Screen Television to the truck!
Moved your Mirror to the truck!
Moved your Guitar to the truck!
Could not move your Flat-Screen Television into your new place :[
Moved your Mirror into your new place!
Moved your Guitar into your new place!
OK, we finished! We were able to move your:
 - Mirror
 - Guitar

But we need to talk about your:
 - Flat-Screen Television

Generic interfaces

A generic interface is an interface that is constrained to a generic type. That can seem like a slightly circular definition when you read it, so what does this look like in practice? Keep going with the moving metaphor.

// 1
interface Container<T> {
  // 2
  fun canAddAnotherItem(): Boolean
  fun addItem(item: T)
  // 3
  fun canRemoveAnotherItem(): Boolean
  fun removeItem(): T
  // 4
  fun getAnother(): Container<T>
  // 5
  fun contents(): List<T>
}

Heads up... You’re accessing parts of this content for free, with some sections shown as cqqotwdol text.

Heads up... You’re accessing parts of this content for free, with some sections shown as gjcudxdyq text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
private fun moveContainerToTruck(container: Container<T>) {
  thingsInTruck.add(container)
  println("Moved a container with your ${container.contents().toBulletedList()} to the truck!")
}

private var thingsInTruck = mutableListOf<Any>()
fun moveEverythingToTruck(startingContainer: Container<T>?) {

Heads up... You’re accessing parts of this content for free, with some sections shown as bxmenqnyz text.

Heads up... You’re accessing parts of this content for free, with some sections shown as mmxyszsev text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
var currentContainer = startingContainer
currentContainer?.let { moveContainerToTruck(it)}
// 1
if (currentContainer != null) {
  // 2
  if (!currentContainer.canAddAnotherItem()) {
    moveContainerToTruck(currentContainer)
    currentContainer = currentContainer.getAnother()
  }
  // 3
  currentContainer.addItem(item)
  println("Packed your $item!")
} else {
  // 4
  thingsInTruck.add(item)
  println("Moved your $item to the truck!")
}

Type erasure

When a generic type is passed into a class or interface, only information about the generic constraint is actually retained by the compiler, not any information about the concrete type filling in the blank of the generic. This is known as type erasure.

Heads up... You’re accessing parts of this content for free, with some sections shown as bblihzren text.

Heads up... You’re accessing parts of this content for free, with some sections shown as ddqoknnoc text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
private fun tryToMoveItemIntoNewPlace(item: T) {
  if (item.checkIsOK()) {
    thingsInNewPlace.add(item)
    println("Moved your $item into your new place!")
  } else {
    thingsWhichFailedCheck.add(item)
    println("Could not move your $item into your new place :[")
  }
}
if (item is T) {}

if (item is Container<T>) {}

Star projection

Replace the T in Container<T> with an asterisk:

if (item is Container<*>) {}

Heads up... You’re accessing parts of this content for free, with some sections shown as xbnaxwqyv text.

Heads up... You’re accessing parts of this content for free, with some sections shown as kpvybqjek text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
if (item is Container<*>) {
  val itemInContainer = item.removeItem()
}

Reified type parameters

Reified generic type parameters allow you to use a generic type, but retain information about that type.

inline fun <reified R> Iterable<*>.filterIsInstance(): List<R>

Heads up... You’re accessing parts of this content for free, with some sections shown as shfustwil text.

Heads up... You’re accessing parts of this content for free, with some sections shown as ncliccgyx text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
val breakableThings = thingsInTruck.filterIsInstance<BreakableThing>()

val items = thingsInTruck.filterIsInstance<T>()

val containers = thingsInTruck.filterIsInstance<Container<*>>()

Heads up... You’re accessing parts of this content for free, with some sections shown as npbuvfdig text.

Heads up... You’re accessing parts of this content for free, with some sections shown as hxpepttov text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
val containers = thingsInTruck.filterIsInstance<Container<T>>()
for (container in containers) {
  thingsInTruck.remove(container)
  while (container.canRemoveAnotherItem()) {
    val itemInContainer = container.removeItem()
    println("Unpacked your $itemInContainer!")
    tryToMoveItemIntoNewPlace(itemInContainer)
  }
}
while (thingsInTruck.count() > 0) {
  val item = thingsInTruck.removeAt(0) as? T
  if (item != null) {
    tryToMoveItemIntoNewPlace(item)
  } else {
    println("Something in the truck was not of the expected generic type: $item")
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as kghojdzux text.

Heads up... You’re accessing parts of this content for free, with some sections shown as rplyrnjus text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
// 1
class CardboardBox: Container<BreakableThing> {
  //2
  private var items = mutableListOf<BreakableThing>()

  override fun contents(): List<BreakableThing> {
    // 3
    return items.toList()
  }

  // 4
  override fun canAddAnotherItem(): Boolean {
  	return items.count() < 2
  }

  override fun addItem(item: BreakableThing) {
    // 5
    items.add(item)
  }

  override fun canRemoveAnotherItem(): Boolean {
    // 6
    return items.count() > 0
  }

  override fun removeItem(): BreakableThing {
    // 7
    val lastItem = items.last()
    items.remove(lastItem)
    return lastItem
  }

  override fun getAnother(): Container<BreakableThing> {
    // 8
    return CardboardBox()
  }
}

Heads up... You’re accessing parts of this content for free, with some sections shown as xhgujkpaf text.

Heads up... You’re accessing parts of this content for free, with some sections shown as lkmengvum text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
cheapMover.moveEverythingToTruck(null)
expensiveMover.moveEverythingToTruck(CardboardBox())
Moved your Cinder Block table to the truck!
Moved your Box of old books to the truck!
Moved your Ugly old couch to the truck!
Moved your Cinder Block table into your new place!
Moved your Box of old books into your new place!
Moved your Ugly old couch into your new place!
OK, we finished! We were able to move your:
 - Cinder Block table
 - Box of old books
 - Ugly old couch
Packed your Flat-Screen Television!
Packed your Mirror!
Moved a container with your
 - Flat-Screen Television
 - Mirror
 to the truck!
Packed your Guitar!
Moved a container with your
 - Guitar
 to the truck!
Unpacked your Mirror!
Moved your Mirror into your new place!
Unpacked your Flat-Screen Television!
Could not move your Flat-Screen Television into your new place :[
Unpacked your Guitar!
Moved your Guitar into your new place!
OK, we finished! We were able to move your:
 - Mirror
 - Guitar

But we need to talk about your:
 - Flat-Screen Television

Heads up... You’re accessing parts of this content for free, with some sections shown as dtcyqhnaf text.

Heads up... You’re accessing parts of this content for free, with some sections shown as dffapjref text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Generic type variance (a.k.a., in and out declarations)

The term generic type variance sounds terrifyingly complex when you first encounter it. This concept is nowhere near as complicated as it sounds.

interface Container<out T> {

interface Container<in T> {

Heads up... You’re accessing parts of this content for free, with some sections shown as rvhasknal text.

Heads up... You’re accessing parts of this content for free, with some sections shown as bpsorfham text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

interface Container<T> {
val ints = listOf(1, 2, 3)
val numbers: List<Number> = ints

Heads up... You’re accessing parts of this content for free, with some sections shown as zcwowzhal text.

Heads up... You’re accessing parts of this content for free, with some sections shown as zcfeglbut text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
val moreInts: List<Int> = numbers

val mutableInts = mutableListOf(1, 2, 3)
val mutableNumbers: MutableList<Number> = mutableInts

Heads up... You’re accessing parts of this content for free, with some sections shown as qmsymfral text.

Heads up... You’re accessing parts of this content for free, with some sections shown as xwwanwhit text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

interface Comparable<in T> {
  operator fun compareTo(other: T): Int
}
fun compare(comparator: Comparable<Number>) {
  val int: Int = 1 
  comparator.compareTo(int)
  val float: Float = 1.0f
  comparator.compareTo(float)
}
val intComparable: Comparable<Int> = comparator

Heads up... You’re accessing parts of this content for free, with some sections shown as hffivqfeb text.

Heads up... You’re accessing parts of this content for free, with some sections shown as jtrogknyt text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
intComparable.compareTo(int)
intComparable.compareTo(float)

Challenges

Heads up... You’re accessing parts of this content for free, with some sections shown as qzwutxxij text.

Heads up... You’re accessing parts of this content for free, with some sections shown as nvsahdneb text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Key points

Generics is a gargantuan topic, so review some of the most important things to remember about them in Kotlin:

Where to go from here?

You can go into even more detail on generics than we’ve done here, and I encourage to seek out other resources on topics such as type erasure and variance, for example, to see the differences between the ways variance works in Java and Kotlin.

Heads up... You’re accessing parts of this content for free, with some sections shown as kwqiwztiw text.

Heads up... You’re accessing parts of this content for free, with some sections shown as fbrujvzud text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2025 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as cjtoqtpej text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now