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
| Kotlin | Java |
|---|---|
Int | int |
Double | double |
Boolean | boolean |
Int? | java.lang.Integer |
Double? | java.lang.Double |
Boolean? | java.lang.Boolean |
Array<Int> | Integer[] |
IntArray | int[] |
List<Int> | List<Integer> |
kotlin.String | java.lang.String |
Any | java.lang.Object |
() -> Boolean | kotlin.jvm.functions.Function0<Boolean> |
(Order) -> Int | kotlin.jvm.functions.Function1<Order, Int> |
(Int, Int) -> Int | kotlin.jvm.functions.Function2<Int, Int, Int> |
Unit | void |
Nothing | void |
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
Unitinstead ofvoid- no meaningful value is returned
// those are equivalent syntatic forms
fun f() {}
fun f(): Unit {}
Nothingmeans '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>?"
| Modifier | Name | Meaning | Is Generic<A> a subtype of Generic<B>? |
|---|---|---|---|
out | Covariant | Read-only: we can get values of type A but not put them in | ✅ |
in | Contravariant | Write-only: we can put values of type A but not read them out fully | ✅ (inverse) |
| nothing | Invariant | We can both read and write, but type must match exactly | ❌ |