Overview
Generic functions are functions that accept a type parameter, which allows different call sites to pass different types, without sacrificing type safety. Yeah, that’s a mouthful, so let’s just take a look at an example.
Example
Definition
Here’s a Kotlin generic function that returns the middle-most item out of a List
:
fun <T> middleItem(list: List<T>): T = list[list.size / 2]
This example specifies:
- A type parameter of type
T
. This type parameter declaration goes before the name of the function, and it’s written here as<T>
. - An argument that accepts a
List
of items that are of typeT
. - A return type of
T
, which is the same as the type of the items in theList
.
What exactly is the type of T
? Well, it depends on the call sites, so let’s look at how we’d call this function.
Usage
val middleInteger: Int = middleItem(listOf(1, 2, 3, 4, 5))
val middleString: String = middleItem(listOf("one", "two", "three"))
As this code demonstrates, our generic function can work with both List<Int>
and List<String>
. (In fact, it can work with a List
of any type!) When this function returns the result, the type is preserved. In other words, we didn’t have to cast the result back to an Int
or String
.
Comparison with Java
Generic functions in Kotlin are similar to Java, but the type parameter is declared in slightly different locations:
In Kotlin, the type parameter declaration |
In Java, the type parameter declaration |
This shouldn’t trip you up much, since the real difference is that, in Kotlin, the return type has simply been moved to be after the function.
Alternatives
Abstract Arguments and/or Return Types
An alternative to our generic function above would be a function that accepts a more abstract type. For example, here we’ve got a function that accepts a List<Any>
and returns Any
.
fun middleItem(list: List<Any>): Any = list[list.size / 2]
As mentioned above, the trade-off is the casting that’s required at the call sites. Notice that we have to add as Int
and as String
here:
val middleInteger: Int = middleItem(listOf(1, 2, 3, 4, 5)) as Int
val middleString: String = middleItem(listOf("one", "two", "three")) as String
It makes sense to use a generic function when you want to preserve the type. Otherwise, if you just need to invoke a function polymorphically, then accepting a particular abstract type is appropriate.