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.
Here’s a Kotlin generic function that returns the middle-most item out of a
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
- An argument that accepts a
Listof items that are of type
- A return type of
T, which is the same as the type of the items in the
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.
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<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
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.
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
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.