Author profile picture

Type Parameter Declaration

Overview

Generics allow you to create interfaces, classes, functions, and extension properties that can work with a “fill-in-the-blank” type, where the calling code can choose the type that should be used.

To create a generic, you must declare the type parameter. This article covers a variety of ways that you can do that.

Examples

Function

fun <T> middleItem(list: List<T>): T = list[list.size / 2]

In this example, the <T> between fun and middleItem is the type parameter declaration. It declares that this function has a type parameter, named T, which can be referenced later on in the function. In this example, it’s referenced twice: once as part of the List type, and once as the return type.

Extension Function

The example above can be rewritten as an extension function. And it probably should be, so that you can take advantage of the safe-call operator. The type parameter declaration is in the same spot for an extension method - after fun.

fun <T> List<T>.middleItem(): T = this[this.size / 2]

Extension Property

We can also implement the same logic as an extension property. In this case, the type parameter declaration goes in the same spot, but of course the preceeding keyword for an extension property is val instead of fun.

val <T> List<T>.middleItem: T
    get() = this[this.size / 2]

Interfaces

Unlike functions and extension properties, when declaring a type parameter on an interface, the declaration goes after the interface name:

interface Basket<T> {
	// ...
}

Classes

Similar to interfaces, the type parameter declaration goes after the class name:

class Barrel<T> {
	// ...
}

Multiple Type Parameters

You can specify more than one type parameter in your declaration by separating them with a comma:

interface KeyValuePair<K, V> {
    fun get(key: K): V
}

Constraints

There are times when you want to limit the types that can be used for the parameter. For example, the following Box can only store Donut objects, or subtypes of Donut. We call this an upper bound. To specify an upper bound, in the declaration, follow the type parameter name with a : and the name of the upper-most type you want to allow.

class Box<T : Donut>(private val collection: Collection<T>)

If no constraint is specified, it’s effectively limited to the Any? type.

For all the details, check out the dedicated article, Type Parameter Constraint.

Variance

To specify the variance, use a variance annotation on the type parameter, marking it as in for contravariance or out for covariance. By default, the generic will be invariant on its type parameter.

interface KeyValuePair<in K, out V> {
    fun get(key: K): V
}

Reification

A type parameter can also be marked reified. For an introduction to reified type parameters, check out the guide, Getting Real with Reified Type Parameters. Otherwise, for an in-depth look, see the concept article, Reified Type Parameter.

Shadowing

It’s possible to inadvertently shadow the type parameter declaration of a class or interface by redefining the type parameter on a function. For example:

class Widget<T>(val item: T) {
    fun <T> sameHashCodeAs(other: T) = 
            item?.hashCode() == other?.hashCode()
}

In this case, since item and other are both of type T, you might expect them to be of the same type. But because the function sameHashCodeAs() has its own type parameter declaration, it shadows the type parameter declaration of Widget, so they can be invoked with two different types:

// Compiles and executes just fine...
Widget("Hello").sameHashCodeAs(3)

If you want item and other to have the same type, then omit the declaration from the function:

class Widget<T>(val item: T) {
    fun sameHashCodeAs(other: T) =
            item?.hashCode() == other?.hashCode()
}

This will cause the T in the function argument to come from the declaration of the Widget class instead.

On the other hand, if you do intend for the function argument to be a different type, it’d naturally be a good idea to name the function’s type parameter something different from the class’ type parameter.

Share this article: