Author profile picture

Type Argument

A type argument is a specific type, specified by the calling code, that a generic will use in place of a type parameter.

If your generic were a light fixture, then the light bulb that’s screwed into that light fixture is the type argument. Just as different kinds of light bulbs can be used in a light fixture’s socket, different call sites can each specify a different type argument to use in instances of a generic.

Examples

Type Declaration

val numbers: Array<Number> = arrayOf(1, 2, 3)

Here we’re specifying the type of numbers. In this case, the type argument is Number.

Function Call

Instead of specifying the type argument on the variable’s type declaration, you can specify it on the function call (or constructor invocation) instead. That looks like this:

val numbers = arrayOf<Number>(1, 2, 3)

Type Inference

In some cases, the compiler can infer the type argument based on the context. For example, you can omit the type argument in the examples above, like so:

val numbers = arrayOf(1, 2, 3)

In this case, there’s still a type argument; you just don’t have to explicitly declare it. In this case, the type argument is Int, because all of the arguments to arrayOf() are of type Int.

This might or might not be what you wanted. For instance, you might have wanted an Array<Number> instead of an Array<Int>. In that case, you’d have to specify the type argument explicitly like we did in the first two examples.

Type Projection

Normally, generics are invariant, which means that an Array<Number> is not a supertype of Array<Int>, even though Number is a supertype of Int.

This might be inconvenient for you. For example, if you’re writing a function that takes an Array<Number>, you might want to accept these types as well:

  • Array<Int>
  • Array<Long>
  • Array<Float>
  • Array<Double>

In this case, as long as you won’t need to set a value in the array (that is, as long as you’re only going to be reading values from it), you can achieve the subtyping you want by creating a type projection, by specifying a variance annotation on the type argument. For example:

fun toBytes(values: Array<out Number>) =
        values.map(Number::toByte).toTypedArray()

We added the modifier out to the type argument in order to create a type projection. The values argument can now accept an Array of any kind of Number, including Long and Double:

val longs: Array<Long> = arrayOf(255L, 256L, 257L)
toBytes(longs).forEach { println(it) }

val doubles: Array<Double> = arrayOf(255.0, 256.0, 257.0)
toBytes(doubles).forEach { println(it) }

Type Argument vs. Type Parameters

Also, keep in mind that type arguments are not the same as type parameters – type arguments are the light bulbs and type parameters are the sockets:

Code snippet demonstrating a type parameter and a type argument.

Share this article: