Kotlin Generic Constraints
A bound limits which types a type parameter can accept. Variance sets how generic types with related arguments fit together.
A Comparable bound
The bound T : Comparable<T> lets biggest compare its three values. Whole numbers and words both meet the bound.
fun <T : Comparable<T>> biggest(first: T, second: T, third: T): T {
var best = first
if (second > best) {
best = second
}
if (third > best) {
best = third
}
return best
}
fun main() {
println(biggest(3, 9, 4))
println(biggest("ant", "cat", "bee"))
}A where clause with two bounds
The where clause asks for values that are both Comparable and Number. Whole numbers and decimal numbers both meet the two bounds.
fun <T> largestScore(values: List<T>): T where T : Comparable<T>, T : Number {
var best = values[0]
for (value in values) {
if (value > best) {
best = value
}
}
return best
}
fun main() {
println(largestScore(listOf(2, 8, 5)))
println(largestScore(listOf(1.5, 3.5, 2.5)))
}Producing values with out
Source only hands values out, so it marks its type with out. A Source of String can stand where a Source of Any is expected.
interface Source<out T> {
fun next(): T
}
class WordSource(private val word: String) : Source<String> {
override fun next(): String {
return word
}
}
fun main() {
val words: Source<String> = WordSource("hello")
val anything: Source<Any> = words
println(anything.next())
}Taking values in with in
Sink only takes values in, so it marks its type with in. A Sink of Any can stand where a Sink of String is expected.
interface Sink<in T> {
fun accept(value: T)
}
class AnySink : Sink<Any> {
override fun accept(value: Any) {
println(value)
}
}
fun main() {
val anySink: Sink<Any> = AnySink()
val stringSink: Sink<String> = anySink
stringSink.accept("note")
}A type outside the bound fails
Token has no order, so it does not meet the Comparable bound. The compiler cannot infer T and then reports that a Token does not match the expected Comparable type.
class Token(val label: String)
fun <T : Comparable<T>> biggest(first: T, second: T): T {
return if (first >= second) first else second
}
fun main() {
val left = Token("a")
val right = Token("b")
println(biggest(left, right))
}Example
fun <T : Comparable<T>> smallest(values: List<T>): T {
var best = values[0]
for (value in values) {
if (value < best) {
best = value
}
}
return best
}
fun main() {
println(smallest(listOf(7, 2, 9)))
println(smallest(listOf("pear", "fig", "plum")))
}Key points
- A bound sits after the type parameter with a colon and a type name.
- A Comparable bound lets the body compare values with < and >.
- A where clause lists more than one bound for a type parameter.
- The out keyword marks a type that only produces values of its type.
- The in keyword marks a type that only takes in values of its type.
- A call that breaks a bound fails at compile time.
In this example, smallest walks a list and keeps the smallest value it has seen. The bound lets the body compare each value with <. The number list gives 2 and the word list gives fig.
Try it Yourself
Edit the code, then press Run.
Running is simulated ยท Kotlin Playground
Exercise
What does the bound in fun <T : Comparable<T>> biggest(a: T, b: T) require?