# Introduction to Generics

Generics create parameterized types: components that work across multiple types.

The term “generic” means “pertaining or appropriate to large groups of classes.” The original intent of generics in programming languages was to provide the programmer maximum expressiveness when writing classes or functions, by loosening type constraints on those classes or functions.

One of the most compelling initial motivations for generics is to create collection classes, which you’ve seen in the Lists, Sets and Maps used for the examples in this book. A collection is an object that holds other objects. Many programs require you to hold a group of objects while you use them, so collections are one of the most reusable of class libraries.

Let’s look at a class that holds a single object. This class specifies the exact type of that object:

// IntroGenerics/RigidHolder.kt
package introgenerics
import atomictest.eq

data class Automobile(val brand: String)

class RigidHolder(private val a: Automobile) {
  fun getValue() = a
}

fun main() {
  val holder = RigidHolder(Automobile("BMW"))
  holder.getValue() eq
    "Automobile(brand=BMW)"
}

RigidHolder is not a particularly reusable tool; it can’t hold anything but an Automobile. We would prefer not to write a new type of holder for every different type. To achieve this, we use a type parameter instead of Automobile.

To define a generic type, add angle brackets (<>) containing one or more generic placeholders and put this generic specification after the class name. Here, the generic placeholder T represents the unknown type and is used within the class as if it were a regular type:

// IntroGenerics/GenericHolder.kt
package introgenerics
import atomictest.eq

class GenericHolder<T>(               // [1]
  private val value: T
) {
  fun getValue(): T = value
}

fun main() {
  val h1 = GenericHolder(Automobile("Ford"))
  val a: Automobile = h1.getValue()   // [2]
  a eq "Automobile(brand=Ford)"

  val h2 = GenericHolder(1)
  val i: Int = h2.getValue()          // [3]
  i eq 1

  val h3 = GenericHolder("Chartreuse")
  val s: String = h3.getValue()       // [4]
  s eq "Chartreuse"
}
  • [1] GenericHolder stores a T, and its member function getValue() returns a T.

When you call getValue() as in [2], [3] or [4] , the result is automatically the right type.

It seems like we might be able to solve this problem with a “universal type”—a type that is the parent of all other types. In Kotlin, this universal type is called Any. As the name implies, Any allows any type of argument. If you want to pass a variety of types to a function and they have nothing in common, Any solves the problem.

At a glance, it looks like we might be able to use Any instead of T in GenericHolder.kt:

// IntroGenerics/AnyInstead.kt
package introgenerics
import atomictest.eq

class AnyHolder(private val value: Any) {
  fun getValue(): Any = value
}

class Dog {
  fun bark() = "Ruff!"
}

fun main() {
  val holder = AnyHolder(Dog())
  val any = holder.getValue()
  // Doesn't compile:
  // any.bark()

  val genericHolder = GenericHolder(Dog())
  val dog = genericHolder.getValue()
  dog.bark() eq "Ruff!"
}

Any does in fact work for simple cases, but as soon as we need the specific type—to call bark() for the Dog—it doesn’t work because we lose track of the fact that it’s a Dog when it is assigned to the Any. When we pass a Dog as an Any, the result is just an Any, which has no bark().

Using generics retains the information that, in this case, we actually have a Dog, which means we can perform Dog operations on the object returned by getValue().

# Generic Functions

To define a generic function, specify a generic type parameter in angle brackets before the function name:

// IntroGenerics/GenericFunction.kt
package introgenerics
import atomictest.eq

fun <T> identity(arg: T): T = arg

fun main() {
  identity("Yellow") eq "Yellow"
  identity(1) eq 1
  val d: Dog = identity(Dog())
  d.bark() eq "Ruff!"
}

d has type Dog because identity() is a generic function and returns a T.

The Kotlin standard library contains many generic extension functions for collections. To write a generic extension function, put the generic specification before the receiver. For example, notice how first() and firstOrNull() are defined:

// IntroGenerics/GenericListExtensions.kt
package introgenerics
import atomictest.eq

fun <T> List<T>.first(): T {
  if (isEmpty())
    throw NoSuchElementException("Empty List")
  return this[0]
}

fun <T> List<T>.firstOrNull(): T? =
  if (isEmpty()) null else this[0]

fun main() {
  listOf(1, 2, 3).first() eq 1

  val i: Int? =                     // [1]
    listOf(1, 2, 3).firstOrNull()
  i eq 1

  val s: String? =                  // [2]
    listOf<String>().firstOrNull()
  s eq null
}

first() and firstOrNull() work with any kind of List. To return a T, they must be generic functions.

Notice how firstOrNull() specifies a nullable return type. Line [1] shows that calling the function on List<Int> returns the nullable type Int?. Line [2] shows that calling firstOrNull() on List<String> returns String?. Kotlin requires the ? on lines [1] and [2]—take them out and see the error messages.

Exercises and solutions can be found at www.AtomicKotlin.com.