Skip to main content

Types under the hood

There are no primitive types in Kotlin. Correspondence between Kotlin and Java types would look like

Kotlin

fun foo(): Int = 1

Decompiled Java code

public static final int foo() {
return 1;
}

Kotlin

fun bar(): Int? = 1

Decompiled Java code

public static final Integer bar() {
return 1;
}

Kotlin vs Java bytecode types​

KotlinJava
Intint
Doubledouble
Booleanboolean
Int?java.lang.Integer
Double?java.lang.Double
Boolean?java.lang.Boolean
Array<Int>Integer[]
IntArrayint[]
List<Int>List<Integer>
kotlin.Stringjava.lang.String
Anyjava.lang.Object
() -> Booleankotlin.jvm.functions.Function0<Boolean>
(Order) -> Intkotlin.jvm.functions.Function1<Order, Int>
(Int, Int) -> Intkotlin.jvm.functions.Function2<Int, Int, Int>
Unitvoid
Nothingvoid

Any​

In Kotlin Any is not only a super type for all reference types, but it's also a super types for types like Int, corresponding to primitives.

Nothing​

Nothing is a subtype of all the other types. Having Nothing type in the language really helps with type inference in branch logic situations.

Unit vs Nothing vs void​

  1. Unit instead of void - no meaningful value is returned
// those are equivalent syntatic forms
fun f() {}
fun f(): Unit {}
  1. Nothing means 'this function never returns'
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}

Nullability

Kotlin took the approach of making NPE a compile-time exception. Each type is a child of the same nullable type. Under the hood fun foo(): String? = "foo" is


@Nullable
public static final String foo() {
return "foo";
}

fun bar(): String = "bar" is


@NotNull
public static final String foo() {
return "foo";
}

Operators to work with nullability in Kotlin

!!

s!! - throws NPE if s is null

?.

?:

as?

Types​

All nullable types are super types for non-nullable types because we want to be able to assign a User to User? but not vice versa. The simplest example of Nothing? though is null.

Platform type​

Type! notation is used for types that came from Java, it's the type for "unknown" nullability. To avoid possible NPE here it's useful to

  • Annotate Java types
  • Specify types explicitly

Generics

A regular generic argument (T for example) can receive a nullable type. We can make it explicit.

fun <T> List<T>.firstOrNull(): T? {}

At the same time we can set a non-nullable upper bound with Any.

fun <T : Any> foo(list: List<T>) {
for (element in list) {

}
}

If we need to use multiple constraints for a type parameter we must use the where.

fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable {
if (!seq.endsWith('.')) {
seq.append('.')
}
}

In situations when generics result in the same JVM signature we must use @JvmName to resolve the conflict.

fun List<Int>.average(): Duble { }
@JvmName("averageOfDouble")
fun List<Double>.average(): Double { }

Variance

Type variance answers the question: "If A is a subtype of B, is Generic<A> a subtype of Generic<B>?"

ModifierNameMeaningIs Generic<A> a subtype of Generic<B>?
outCovariantRead-only: we can get values of type A but not put them inβœ…
inContravariantWrite-only: we can put values of type A but not read them out fullyβœ… (inverse)
nothingInvariantWe can both read and write, but type must match exactly❌