Author profile picture

Generic Function

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 type T.
  • A return type of T, which is the same as the type of the items in the List.

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:

fun <T> middleItem(list: List<T>): T = // ...

In Kotlin, the type parameter declaration <T> goes before the function name.

<T> T getMiddle(List<T> list) { /* ... */ }

In Java, the type parameter declaration <T> goes before the return type.

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.

Share this article: