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
Listof 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.