Menu
Coddy logo textTech

Type Constraints

Part of the Object Oriented Programming section of Coddy's Kotlin journey. Lesson 39 of 57.

An upper bound restricts a type parameter. <T : Comparable<T>> accepts only types whose values can be compared with each other, so the function may use < and > on them:

fun <T : Comparable<T>> largest(a: T, b: T, c: T): T {
    var best = a
    if (b > best) best = b
    if (c > best) best = c
    return best
}

Inside main:

println(largest(3, 9, 4))
println(largest("pear", "apple", "fig"))

Output:

9
pear

The bound can be any class or interface, including your own. Inside the function, the bound's members are available, and the result still has the caller's exact type:

interface Priced {
    val price: Int
}
data class Book(val title: String, override val price: Int) : Priced
data class Lamp(val watts: Int, override val price: Int) : Priced

fun <T : Priced> cheapest(items: List<T>): T? = items.minByOrNull { it.price }

Inside main:

val book = cheapest(listOf(Book("Dune", 12), Book("Emma", 8)))
println(book?.title)
println(cheapest(listOf(Lamp(60, 30), Lamp(9, 45)))?.watts)

Output:

Emma
60

where lists several bounds for one type parameter; the type argument must satisfy all of them:

interface Named {
    val name: String
}
data class City(override val name: String, val size: Int) : Named, Comparable<City> {
    override fun compareTo(other: City) = size - other.size
}

fun <T> smallestFirst(items: List<T>): String where T : Named, T : Comparable<T> =
    items.sorted().joinToString(" < ") { it.name }

Inside main:

println(smallestFirst(listOf(City("Oslo", 700), City("Bergen", 290), City("Tromso", 77))))

Output:

Tromso < Bergen < Oslo

Without a bound, a type parameter may also be nullable, because its default bound is Any?. <T : Any> rules out nullable types, which is useful when null has a meaning of its own, such as "not found":

fun <T> describe(x: T) = "value: $x"

fun <T : Any> firstMatch(items: List<T>, test: (T) -> Boolean): T? {
    for (item in items) if (test(item)) return item
    return null
}

Inside main:

println(describe(null))
println(firstMatch(listOf(4, 7, 10)) { it > 5 })
println(firstMatch(listOf("a", "b")) { it == "z" })
// firstMatch(listOf(1, null)) { true }   // error: Int? does not satisfy T : Any

Output:

value: null
7
null
challenge icon

Challenge

Easy

Write three generic functions for any type whose values can be compared, with the bound Comparable. clamp(value, min, max) returns the value limited to the range. isSorted(items) returns whether every element is less than or equal to the next. median(items) returns the middle element of the sorted list (the lower of the two middle ones for an even size), or null for an empty list.

The supplied code reads a line of numbers and a line of words. It prints the numbers clamped to 0-10, whether each list is sorted, their medians and the first word clamped between b and m.

Your code goes in Ranges.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 numbers = input[0].split(" ").map { it.toInt() }
    val words = input[1].split(" ")
    println("clamped: " + numbers.map { clamp(it, 0, 10) })
    println("numbers sorted: ${isSorted(numbers)}, median: ${median(numbers)}")
    println("words sorted: ${isSorted(words)}, median: ${median(words)}")
    println("word clamp: ${clamp(words[0], "b", "m")}")
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Kotlin playground