# Creating Generics
Generic code works with types that are “specified later.”
Ordinary classes and functions work with specific types. If you want code to work across more types, this rigidity can be overconstraining.
[Polymorphism](javascript:void(0)) is an object-oriented generalization tool. You write a function that takes a base-class object as a parameter, then call that function with an object of any class derived from that base class—including classes that haven’t yet been created. Now your function is more general, and useful in more places.
A single hierarchy can be too limiting because you must inherit from that hierarchy to produce an object that fits your function parameter. If a function parameter is an interface instead of a class, the limitations are loosened to include anything that implements that interface. This gives the client programmer the option of implementing an interface in combination with an existing class—that is, to adapt an existing class to fit the function. Used this way, interfaces can cut across class hierarchies.
Sometimes even an interface is too restrictive because it forces you to work with only that interface. Your code can be even more general if it works with “some unspecified type,” rather than a particular interface or class. That “unspecified type” is a generic type parameter.
Creating generic types and functions is a fairly complex topic, much of which is outside the scope of this book. This atom attempts to give you just enough background so you aren’t surprised when you come across generic concepts and keywords. If you want to get serious about writing generic types and functions you’ll need to study more advanced resources.
# Any
Any
is the root of the Kotlin class hierarchy. Every Kotlin class has Any
as a superclass. One way to work with unspecified types is by passing Any
arguments, and this can sometimes confuse the issue of when to use generics. If Any
works, it’s the simpler solution, and simpler is generally better.
There are two ways to use Any
. The first, and most straightforward approach, is when you only need to operate on an Any
, and nothing more. This is extremely limiting—Any
has only three member functions: equals()
, hashCode()
and toString()
. There are also extension functions, but these cannot perform any direct operations on the type. For example, apply()
only applies its function argument to the Any
.
If you know the type of the Any
, you can cast it and perform type-specific operations. Because this involves run-time type information (as shown in [Downcasting](javascript:void(0))), you risk a runtime error if you pass the wrong type to your function (there’s also a slight performance impact). Sometimes this is justified to gain the benefit of eliminating code duplication.
For example, suppose three types each have the ability to communicate. They come from different libraries so you can’t just put them in the same hierarchy, and they have different function names for communicating:
// CreatingGenerics/Speakers.kt
package creatinggenerics
import atomictest.eq
class Person {
fun speak() = "Hi!"
}
class Dog {
fun bark() = "Ruff!"
}
class Robot {
fun communicate() = "Beep!"
}
fun talk(speaker: Any) = when (speaker) {
is Person -> speaker.speak()
is Dog -> speaker.bark()
is Robot -> speaker.communicate()
else -> "Not a talker" // Or exception
}
fun main() {
talk(Person()) eq "Hi!"
talk(Dog()) eq "Ruff!"
talk(Robot()) eq "Beep!"
talk(11) eq "Not a talker"
}
The when
expression discovers the type of the speaker
and calls the appropriate function. If you don’t think talk()
will ever need to work with additional types, this is a tolerable solution. Otherwise, it requires you to modify talk()
for each new type you add, and to rely on runtime information to discover when you miss something.
# Defining Generics
Duplicated code is a candidate for conversion into a generic function or type. You do this by adding angle brackets (<>
) containing one or more generic placeholders. Here, the generic placeholder T
represents the unknown type:
// CreatingGenerics/DefiningGenerics.kt
package creatinggenerics
fun <T> gFunction(arg: T): T = arg
class GClass<T>(val x: T) {
fun f(): T = x
}
class GMemberFunction {
fun <T> f(arg: T): T = arg
}
interface GInterface<T> {
val x: T
fun f(): T
}
class GImplementation<T>(
override val x: T
) : GInterface<T> {
override fun f(): T = x
}
class ConcreteImplementation
: GInterface<String> {
override val x: String
get() = "x"
override fun f() = "f()"
}
fun basicGenerics() {
gFunction("Yellow")
gFunction(1)
gFunction(Dog()).bark() // [1]
gFunction<Dog>(Dog()).bark()
GClass("Cyan").f()
GClass(11).f()
GClass(Dog()).f().bark() // [2]
GClass<Dog>(Dog()).f().bark()
GMemberFunction().f("Amber")
GMemberFunction().f(111)
GMemberFunction().f(Dog()).bark() // [3]
GMemberFunction().f<Dog>(Dog()).bark()
GImplementation("Cyan").f()
GImplementation(11).f()
GImplementation(Dog()).f().bark()
ConcreteImplementation().f()
ConcreteImplementation().x
}
basicGenerics()
shows that each generic handles different types:
gFunction()
takes a parameter of typeT
and returns aT
result.GClass
stores aT
. Its member functionf()
returns aT
.GMemberFunction
parameterizes a member function within the class, rather than parameterizing the entire class.- You can also define an
interface
with generic parameters as shown inGInterface
. An implementation ofGInterface
can either redefine a type parameter as inGImplementation
, or provide a specific type argument, as inConcreteImplementation
.
Notice in [1], [2] and [3] that we are able to call bark()
on the result, because that result emerges as type Dog
.
Consider [1], [2] and [3], and the lines immediately following them. The type T
is determined by type inference for [1], [2] and [3]. Sometimes this is not possible if a generic or its invocation is too complex to be parsed by the compiler. In this case you must specify the type(s) using the syntax shown in the lines immediately following [1], [2] and [3].
# Preserving Type Information
As you will see later in this atom, code within generic classes and functions can’t know the type of T
—this is called erasure. Generics can be thought of as a way to preserve type information for the return value. This way, you don’t have to write code to explicitly check and cast a return value to the desired type.
A common use of generic code is for containers that hold other objects. Consider a CarCrate
class that acts as a trivial collection by holding and producing a single element of type Car
:
// CreatingGenerics/CarCrate.kt
package creatinggenerics
import atomictest.eq
class Car {
override fun toString() = "Car"
}
class CarCrate(private var c: Car) {
fun put(car: Car) { c = car }
fun get(): Car = c
}
fun main() {
val cc = CarCrate(Car())
val car: Car = cc.get()
car eq "Car"
}
When we call cc.get()
, the result comes back as type Car
. We’d like to make this tool available to more objects than just Car
s, so we generify this class as Crate<T>
:
// CreatingGenerics/Crate.kt
package creatinggenerics
import atomictest.eq
open class Crate<T>(private var contents: T) {
fun put(item: T) { contents = item }
fun get(): T = contents
}
fun main() {
val cc = Crate(Car())
val car: Car = cc.get()
car eq "Car"
}
Crate<T>
ensures that you can only put()
a T
into the Crate
, and when you call get()
on that Crate
, the result comes back as type T
.
We can make a version of map()
for Crate
by defining a generic extension function:
// CreatingGenerics/MapCrate.kt
package creatinggenerics
import atomictest.eq
fun <T, R> Crate<T>.map(f:(T) -> R): List<R> =
listOf(f(get()))
fun main() {
Crate(Car()).map { it.toString() + "x" } eq
"[Carx]"
}
map()
returns the List
of results produced by applying f()
to each element in the input sequence. Because Crate
only contains a single element, the result is always a List
of one element. There are two generic arguments: T
for the input value and R
for the result, allowing f()
to produce a result type that is different from the input type.
# Type Parameter Constraints
A type parameter constraint says that the generic argument type must be inherited from the constraint. <T : Base>
means that T
must be of type Base
or something derived from Base
. This section shows that using constraints is different from a non-generic type that inherits Base
.
Consider a type hierarchy that models different items and ways to dispose of them:
// CreatingGenerics/Disposable.kt
package creatinggenerics
import atomictest.eq
interface Disposable {
val name: String
fun action(): String
}
class Compost(override val name: String) :
Disposable {
override fun action() = "Add to composter"
}
interface Transport : Disposable
class Donation(override val name: String) :
Transport {
override fun action() = "Call for pickup"
}
class Recyclable(override val name: String) :
Transport {
override fun action() = "Put in bin"
}
class Landfill(override val name: String) :
Transport {
override fun action() = "Put in dumpster"
}
val items = listOf(
Compost("Orange Peel"),
Compost("Apple Core"),
Donation("Couch"),
Donation("Clothing"),
Recyclable("Plastic"),
Recyclable("Metal"),
Recyclable("Cardboard"),
Landfill("Trash"),
)
val recyclables =
items.filterIsInstance<Recyclable>()
Using a constraint, we can access properties and functions of the constrained type within a generic function:
// CreatingGenerics/Constrained.kt
package creatinggenerics
import atomictest.eq
fun <T: Disposable> nameOf(disposable: T) =
disposable.name
// As an extension:
fun <T: Disposable> T.name() = name
fun main() {
recyclables.map { nameOf(it) } eq
"[Plastic, Metal, Cardboard]"
recyclables.map { it.name() } eq
"[Plastic, Metal, Cardboard]"
}
We cannot access name
without the constraint.
This achieves the same result without generics:
// CreatingGenerics/NonGenericConstraint.kt
package creatinggenerics
import atomictest.eq
fun nameOf2(disposable: Disposable) =
disposable.name
fun Disposable.name2() = name
fun main() {
recyclables.map { nameOf2(it) } eq
"[Plastic, Metal, Cardboard]"
recyclables.map { it.name2() } eq
"[Plastic, Metal, Cardboard]"
}
Why use a constraint instead of ordinary polymorphism? The answer is in the return type. With generics, the return type can be exact, rather than being upcast to the base type:
// CreatingGenerics/SameReturnType.kt
package creatinggenerics
import kotlin.random.Random
private val rnd = Random(47)
fun List<Disposable>.aRandom(): Disposable =
this[rnd.nextInt(size)]
fun <T: Disposable> List<T>.bRandom(): T =
this[rnd.nextInt(size)]
fun <T> List<T>.cRandom(): T =
this[rnd.nextInt(size)]
fun sameReturnType() {
val a: Disposable = recyclables.aRandom()
val b: Recyclable = recyclables.bRandom()
val c: Recyclable = recyclables.cRandom()
}
Without generics, aRandom()
can only produce a base-class Disposable
, while both bRandom()
and cRandom()
produce a Recyclable
. bRandom()
never accesses any elements of T
, therefore its constraint is pointless and it ends up being the same as cRandom()
, which doesn’t use a constraint.
The only time you need constraints is if you require both of the following:
- Access a function or property.
- Preserve the type when returning it.
// CreatingGenerics/Constraints.kt
package creatinggenerics
import kotlin.random.Random
private val rnd = Random(47)
// Accesses action() but can't
// return the exact type:
fun List<Disposable>.inexact(): Disposable {
val d: Disposable = this[rnd.nextInt(size)]
d.action()
return d
}
// Can't access action() without a constraint:
fun <T> List<T>.noAccess(): T {
val d: T = this[rnd.nextInt(size)]
// d.action()
return d
}
// Access action() and return the exact type:
fun <T: Disposable> List<T>.both(): T {
val d: T = this[rnd.nextInt(size)]
d.action()
return d
}
fun constraints() {
val i: Disposable = recyclables.inexact()
val n: Recyclable = recyclables.noAccess()
val b: Recyclable = recyclables.both()
}
inexact()
is an extension to List<Disposable>
, which allows it to access action()
, but it is not generic so it can only return the base type Disposable
. As a generic, noAccess()
is able to return the exact type of T
, but without a constraint it cannot access action()
. Only when you add the constraint on T
in both()
are you able to access action()
and return the exact type T
.
# Type Erasure
Java compatibility is an essential part of Kotlin. In Java, generics were not part of the original language—they were added years later, after large bodies of code had been written. Forcing generics into Java without breaking existing code required a crucial compromise: the generic types are only available during compilation but are not preserved at runtime—the types are erased. This erasure affects Kotlin.
Let’s pretend erasure doesn’t happen:
// CreatingGenerics/Erasure.kt
package creatinggenerics
fun main() {
val strings = listOf("a", "b", "c")
val all: List<Any> = listOf(1, 2, "x")
useList(strings)
useList(all)
}
fun useList(list: List<Any>) {
// if (list is List<String>) {} // [1]
}
Uncomment line [1] and you’ll see the following error: “Cannot check for instance of erased type: List<String>
”. You can’t test for the generic type at runtime because the type information has been erased.
If erasure didn’t happen, the list might look like this, assuming additional type information is placed at the end of the list (it does not work this way!):

Reified Generics

Erased Generics
You cannot guess type information from the List
contents without analyzing all elements. Checking only the first element from the second list leads you to incorrectly assume that it’s a List<Int>
.
The Kotlin designers decided to follow Java and use erasure, for two reasons:
- Java compatibility.
- Overhead. Storing generic type information significantly increases the memory occupied by a generic
List
orMap
. For example, a standardMap
consists of manyMap.Entry
objects, andMap.Entry
is a generic class. Thus, if generics were reified everywhere by default, each key and value of everyMap.Entry
would contain additional type information.
# Reification of Function Type Arguments
Type information is also erased for generic function calls, which means you can’t do much with a generic parameter inside a function.
To retain type information for function arguments, add the reified
keyword. Consider a function a()
that requires class information to perform its task:
// CreatingGenerics/ReificationA.kt
package creatinggenerics
import kotlin.reflect.KClass
fun <T: Any> a(kClass: KClass<T>) {
// Uses KClass<T>
}
When we call a()
inside a second generic function b()
, we would like to use type information for the generic argument:
// CreatingGenerics/ReificationB.kt
package creatinggenerics
// Doesn't compile because of erasure:
// fun <T: Any> b() = a(T::class)
The type information for T
is erased when this code runs, so b()
won’t compile. You can’t access the class of the generic type parameter inside the function body.
The Java solution is to pass type information into the function by hand:
// CreatingGenerics/ReificationC.kt
package creatinggenerics
import kotlin.reflect.KClass
fun <T: Any> c(kClass: KClass<T>) = a(kClass)
class K
val kc = c(K::class)
Passing explicit type information should be redundant because the compiler knows the type of T
, and could silently pass it for you. This is effectively what the reified
keyword does.
To use reified
, the function must also be inline
:
// CreatingGenerics/ReificationD.kt
package creatinggenerics
inline fun <reified T: Any> d() = a(T::class)
val kd = d<K>()
d()
produces the same effect as c()
, but d()
doesn’t require the class reference as an argument.
reified
tells the compiler to preserve the information about the corresponding type argument. The type information is now available at runtime so you can access it inside the function body.
Reification allows the use of is
with a generic parameter type:
// CreatingGenerics/CheckType.kt
package creatinggenerics
import atomictest.eq
inline fun <reified T> check(t: Any) = t is T
// fun <T> check1(t: Any) = t is T // [1]
fun main() {
check<String>("1") eq true
check<Int>("1") eq false
}
- [1] Without
reified
, the type information is erased so you can’t check whether a given element is an instance ofT
.
In the following example, select()
produces the name
of each Disposable
item of a particular subtype. It uses reified
combined with a constraint:
// CreatingGenerics/Select.kt
package creatinggenerics
import atomictest.eq
inline fun <reified T : Disposable> select() =
items.filterIsInstance<T>().map { it.name }
fun main() {
select<Compost>() eq
"[Orange Peel, Apple Core]"
select<Donation>() eq "[Couch, Clothing]"
select<Recyclable>() eq
"[Plastic, Metal, Cardboard]"
select<Landfill>() eq "[Trash]"
}
The library function filterIsInstance()
is itself defined using the reified
keyword.
# Variance
Combining generics and inheritance produces two dimensions of change. If you have a Container<T>
and you want to assign it to a Container<U>
where T
and U
have an inheritance relationship, you must place constraints upon Container
using the in
or out
variance annotations, depending on how you want to use Container
.
Here are three versions of a Box
container: a basic Box<T>
, one using <in T>
and one using <out T>
:
// CreatingGenerics/InAndOutBoxes.kt
package variance
class Box<T>(private var contents: T) {
fun put(item: T) { contents = item }
fun get(): T = contents
}
class InBox<in T>(private var contents: T) {
fun put(item: T) { contents = item }
}
class OutBox<out T>(private var contents: T) {
fun get(): T = contents
}
in T
means that member functions of the class can only accept arguments of type T
, but cannot return values of type T
. That is, T
objects can be placed into an InBox
, but cannot come out.
out T
means that member functions can return T
objects, but cannot accept arguments of type T
—you cannot place T
objects into an OutBox
.
Why do we need these constraints? Consider this hierarchy:
// CreatingGenerics/Pets.kt
package variance
open class Pet
class Cat : Pet()
class Dog : Pet()
Cat
and Dog
are both subtypes of Pet
. Is there a subtyping relation between Box<Cat>
and Box<Pet>
? It seems like we should be able to assign, for example, a Box
of Cat
to a Box
of Pet
or to a Box
of Any
(because Any
is a supertype of everything):
// CreatingGenerics/BoxAssignment.kt
package variance
val catBox = Box<Cat>(Cat())
// val petBox: Box<Pet> = catBox
// val anyBox: Box<Any> = catBox
If Kotlin allowed this, petBox
would have put(item: Pet)
. Dog
is also a Pet
, so this would allow you to put a Dog
into catBox
, violating the “cat-ness” of that Box
.
Worse, anyBox
would have put(item: Any)
, so you could put an Any
into catBox
—the container would have no type safety at all.
If we prevent the use of put()
, the assignments are safe because no one can put a Dog
into an OutBox<Cat>
. The compiler allows us to assign an OutBox<Cat>
to an OutBox<Pet>
or to an OutBox<Any>
, because the out
annotation prevents them from having put()
functions:
// CreatingGenerics/OutBoxAssignment.kt
package variance
val outCatBox: OutBox<Cat> = OutBox(Cat())
val outPetBox: OutBox<Pet> = outCatBox
val outAnyBox: OutBox<Any> = outCatBox
fun getting() {
val cat: Cat = outCatBox.get()
val pet: Pet = outPetBox.get()
val any: Any = outAnyBox.get()
}
With no put()
, we cannot place a Dog
into an OutBox<Cat>
, so its “cat-ness” is preserved.
Without a get()
, an InBox<Any>
can be assigned to an InBox<Pet>
, an InBox<Cat>
or an InBox<Dog>
:
// CreatingGenerics/InBoxAssignment.kt
package variance
val inBoxAny: InBox<Any> = InBox(Any())
val inBoxPet: InBox<Pet> = inBoxAny
val inBoxCat: InBox<Cat> = inBoxAny
val inBoxDog: InBox<Dog> = inBoxAny
fun main() {
inBoxAny.put(Any())
inBoxAny.put(Pet())
inBoxAny.put(Cat())
inBoxAny.put(Dog())
inBoxPet.put(Pet())
inBoxPet.put(Cat())
inBoxPet.put(Dog())
inBoxCat.put(Cat())
inBoxDog.put(Dog())
}
It is safe to put()
an Any
, Pet
, Cat
or Dog
into an InBox<Any>
, while you can only put()
a Pet
, Cat
or Dog
into an InBox<Pet>
. inBoxCat
and inBoxDog
will only accept Cat
s and Dog
s, respectively. These are the behaviors we expect for boxes that have those type parameters, and the compiler enforces it.
Here’s a summary of the subtyping relationships for Box
, OutBox
and InBox
:

Variance
Box<T>
is invariant. This means that neitherBox<Cat>
norBox<Pet>
is a subtype of the other, so neither can be assigned to the other.OutBox<out T>
is covariant. This means thatOutBox<Cat>
is a subtype ofOutBox<Pet>
. When you upcast anOutBox<Cat>
to anOutBox<Pet>
, it varies in the same way as upcasting aCat
to aPet
.InBox<in T>
is contravariant. This means thatInBox<Pet>
is a subtype ofInBox<Cat>
. When you upcast anInBox<Pet>
to anInBox<Cat>
, it varies in the opposite way as upcasting aCat
to aPet
.
A read-only List
from the Kotlin standard library is covariant. You can assign a List<Cat>
to a List<Pet>
. A MutableList
is invariant because it contains an add()
:
// CreatingGenerics/CovariantList.kt
package variance
fun main() {
val catList: List<Cat> = listOf(Cat())
val petList: List<Pet> = catList
var mutablePetList: MutableList<Pet> =
mutableListOf(Cat())
mutablePetList.add(Dog())
// Type mismatch:
// mutablePetList =
// mutableListOf<Cat>(Cat()) // [1]
}
- [1] If this assignment worked, we could violate the “cat-ness” of the
mutableListOf<Cat>
by adding aDog
.
Functions can have covariant return types. This means that an overriding function can return a type that’s more specific than the function it overrides:
// CreatingGenerics/CovariantReturnTypes.kt
package variance
interface Parent
interface Child : Parent
interface X {
fun f(): Parent
}
interface Y : X {
override fun f(): Child
}
Notice how the overridden f()
in Y
returns a Child
, while f()
in X
returns a Parent
.
This subsection has only been a light introduction to the topic of variance.
- -
Repeated code is a candidate for generic types or functions. This atom only provides a basic grasp of the ideas—if you need deeper understanding you must find it in a more advanced treatment.
Exercises and solutions can be found at www.AtomicKotlin.com.