Generic Classes
Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 38 of 57.
A class can have type parameters too. Box<T> holds a value of any type, and each instance fixes the type: a Box<Int> only accepts Int values:
class Box<T>(var value: T) {
fun describe() = "box with $value"
}Inside main:
val numberBox = Box(42)
val textBox = Box("hello")
numberBox.value += 1
println(numberBox.describe())
println(textBox.describe())
// numberBox.value = "x" // error: a String is not an IntOutput:
box with 43
box with helloContainers are the typical generic classes. One Stack<T> implementation works for every element type, and the compiler checks what goes in and comes out:
class Stack<T> {
private val items = mutableListOf<T>()
val size: Int
get() = items.size
fun push(item: T) { items.add(item) }
fun pop(): T? = items.removeLastOrNull()
fun peek(): T? = items.lastOrNull()
}Inside main:
val stack = Stack<String>()
stack.push("a")
stack.push("b")
println(stack.pop())
println(stack.peek())
println(stack.size)Output:
b
a
1The type argument is inferred from the constructor arguments when there are any, as in Box(42). Otherwise write it: Stack<String>(). Instances with different type arguments are different types, so a Stack<String> is never mixed up with a Stack<Int>. Functions of a generic class can also add type parameters of their own:
class Box<T>(val value: T) {
fun <R> map(f: (T) -> R): Box<R> = Box(f(value))
}Inside main:
val length = Box("kotlin").map { it.length }
println(length.value + 1)Output:
7Generic interfaces follow the same rules. A class can implement one generically, or for one particular type argument:
interface Repository<T> {
fun add(item: T)
fun all(): List<T>
}
class MemoryRepository<T> : Repository<T> {
private val items = mutableListOf<T>()
override fun add(item: T) { items.add(item) }
override fun all() = items.toList()
}
class TagRepository : Repository<String> {
private val tags = sortedSetOf<String>()
override fun add(item: String) { tags.add(item.lowercase()) }
override fun all() = tags.toList()
}Inside main:
val ids = MemoryRepository<Int>()
ids.add(3); ids.add(1)
val tags = TagRepository()
tags.add("Kotlin"); tags.add("JVM")
println("${ids.all()} ${tags.all()}")Output:
[3, 1] [jvm, kotlin]Challenge
EasyWrite the generic class History<T>(limit) that remembers the last limit items. add(item) appends an item and drops the oldest when there are more than limit. latest is the newest item or null, and all() returns the items from oldest to newest as a read-only list. Then write the extension History<Int>.average(), which exists only for histories of numbers and returns the integer average of the kept numbers (0 when empty).
The supplied code reads the limit, a line of words and a line of numbers. It fills a History<String> and a History<Int> and prints the kept words, the latest word, the kept numbers, their average and empty latest: null.
Your code goes in History.kt. Main.kt holds the supplied input/output code and cannot be edited.
Try it yourself
fun main() {
// Supplied input/output code: keep it as it is
val input = generateSequence(::readLine).toList()
val limit = input[0].toInt()
val words = History<String>(limit)
for (w in input[1].split(" ")) words.add(w)
val numbers = History<Int>(limit)
for (n in input[2].split(" ")) numbers.add(n.toInt())
println("words: ${words.all()}")
println("latest word: ${words.latest}")
println("numbers: ${numbers.all()}")
println("average: ${numbers.average()}")
println("empty latest: ${History<Char>(3).latest}")
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesPrimary Constructorsinit Blocks and ValidationSecondary ConstructorsRecap - Library Card4Inheritance
Open Classes and SubclassesOverriding Memberssuper and ConstructorsAbstract ClassesRecap - Staff Directory2Properties and Encapsulation
Custom AccessorsVisibility ModifiersPrivate Setterslazy and lateinitRecap - Bank Account5Interfaces
Declaring InterfacesDefault MethodsMultiple InterfacesInterfaces as TypesRecap - Payment Methods8Generics
Generic FunctionsGeneric ClassesType ConstraintsVariance With in and outRecap - Priority Queue11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Kotlin playground