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