Kotlin-Grundlagen Teil 2 – Typen in Kotlin verstehen

val vs. var, Typinferenz, Null-Safety und warum Kotlin nichts implizit umwandelt: die Grundlagen des Kotlin-Typsystems – mit Übungsaufgaben zum Selbermachen.

Kotlin

Kotlin-Grundlagen Teil 2 – Typen in Kotlin verstehen

Marcel Strahl5 Min. Lesezeit

Kotlin-Grundlagen Teil 2 – Typen in Kotlin verstehen

Im ersten Teil ging es darum, warum Kotlin viel mehr ist als die Android-Sprache.

Jetzt geht es richtig los. Und zwar da, wo jede Sprache anfängt: bei den Typen.

Kotlin ist statisch typisiert, versteckt das aber gut: Dank Typinferenz schreibt man Typen selten hin – trotzdem kennt der Compiler jeden einzelnen. Und an einer Stelle ist Kotlins Typsystem richtig besonders: Es kennt den Unterschied zwischen „kann null sein“ und „kann nie null sein“.


Was ist überhaupt ein Typ?

Ein Typ sagt dem Compiler, was ein Wert ist und was man damit machen darf.

42 ist eine Zahl. Damit kann man rechnen. "42" ist Text, der nur zufällig aussieht wie eine Zahl.

Zum Ausprobieren brauchst du nichts installieren: Der Kotlin Playground läuft im Browser. Alternativ startet kotlin im Terminal eine REPL.

fun main() {
    println(42)          // 42
    println("42")        // 42 – sieht gleich aus, ist aber ein String
    println(42::class)   // class kotlin.Int
    println("42"::class) // class kotlin.String
}

::class zeigt dir jederzeit, was ein Wert wirklich ist. Unsicher? Reinschauen. Fertig.


val und var – erst die Variablen, dann die Typen

Bevor es um Typen geht, die wichtigste Kotlin-Gewohnheit überhaupt:

val name = "Marcel" // unveränderlich – Zuweisung nur einmal
var alter = 34      // veränderlich
 
alter = 35          // ok
// name = "Max"     // Compilerfehler

Regel ab Tag 1: Immer val, außer man braucht wirklich var. Je weniger sich ändern kann, desto weniger kann kaputtgehen.

Und der Typ? Steht da gar nicht – der Compiler leitet ihn her. name ist trotzdem fest ein String:

val name = "Marcel"
// name = 42 // Compilerfehler: Typ steht fest, auch wenn er nie hingeschrieben wurde

Explizit geht natürlich auch: val name: String = "Marcel". Nötig ist es selten.


Die Basis-Typen

Int – Ganzzahlen

val alter = 34
val temperatur = -5
val jahr = 2026

Ganze Zahlen. Für sehr große Werte gibt es Long (mit L-Suffix: 9_000_000_000L).

Double – Kommazahlen

val preis = 19.99
val pi = 3.14159

Wichtig: Der Dezimaltrenner ist ein Punkt, kein Komma – auch wenn wir im Deutschen „Kommazahl“ sagen.

Doubles haben außerdem eine fiese Falle eingebaut – dazu mehr im Zusatz am Ende des Artikels.

String – Text

val name = "Marcel"
val begruessung = "Hallo $name"           // "Hallo Marcel"
val info = "Nächstes Jahr: ${alter + 1}"  // Ausdrücke in ${...}

String-Templates ersetzen die Verkettung mit + fast komplett – lesbarer und weniger fehleranfällig.

Boolean – Wahr oder Falsch

val istAngemeldet = true
val hatBezahlt = false

Zwei mögliche Werte: true oder false. Kein „truthy“, kein „falsy“ – ein if will in Kotlin einen echten Boolean.

Char – ein einzelnes Zeichen

val note = 'A' // einfache Anführungszeichen, genau ein Zeichen

Keine impliziten Umwandlungen – Kotlins stille Stärke

Jetzt kommt etwas, das Umsteiger aus PHP oder JavaScript erst wundert und dann überzeugt:

Kotlin wandelt nichts automatisch um.

val a: Int = 42
// val b: Long = a        // Compilerfehler!
val b: Long = a.toLong()  // explizit – so will Kotlin das
 
// val summe = "5" + 3    // ergibt "53" – String-Verkettung
// val summe = 3 + "5"    // Compilerfehler – Int + String geht nicht

Kein Type Juggling, keine stillen Überraschungen. Wenn ein Typ sich ändern soll, steht es im Code: toInt(), toDouble(), toString().

Und Vergleiche? In Kotlin ist == der Inhaltsvergleich (intern equals()). Wer wirklich wissen will, ob zwei Referenzen auf dasselbe Objekt zeigen, nimmt ===:

val a = "Hallo"
val b = buildString { append("Hallo") }
 
println(a == b)  // true  – gleicher Inhalt
println(a === b) // false – verschiedene Objekte

Für alle, die aus Java kommen: Ja, genau andersherum als dort. Kotlin hat die Falle einfach entschärft – == tut, was man erwartet.


Null-Safety – das Herzstück

Und jetzt das Feature, für das Kotlin berühmt ist.

In den meisten Sprachen kann jede Variable heimlich null sein. In Kotlin ist „kann null sein“ Teil des Typs:

val name: String = "Marcel"
// name = null // Compilerfehler – String kann nie null sein
 
val mittlererName: String? = null // String? darf null sein

Und sobald ein Typ nullable ist, zwingt der Compiler dich, damit umzugehen:

val mittlererName: String? = null
 
// println(mittlererName.length) // Compilerfehler: könnte null sein!
 
println(mittlererName?.length)        // null – Safe Call: nur zugreifen, wenn nicht null
println(mittlererName?.length ?: 0)   // 0 – Elvis-Operator: Default, falls null
  • ?. – Safe Call: Zugriff nur, wenn der Wert nicht null ist, sonst kommt null raus
  • ?: – Elvis-Operator: Fallback-Wert, falls links null steht
  • !! – „Ich schwöre, das ist nicht null“ – wirft sonst eine Exception. Fast immer ein Code-Smell.

Die NullPointerException, die in Java ganze Karrieren begleitet, ist damit größtenteils wegdesignt. Nicht zur Laufzeit gefangen – zur Compile-Zeit verhindert.


Collections – Listen und Maps

val sprachen = listOf("Kotlin", "PHP", "TypeScript")
println(sprachen[0]) // Kotlin
 
val user = mapOf(
    "name" to "Marcel",
    "alter" to 34,
)
println(user["name"]) // Marcel

Wichtig: listOf und mapOf erzeugen unveränderliche Collections. Wer verändern will, sagt es explizit: mutableListOf, mutableMapOf. Dasselbe Prinzip wie bei val/var.

Für strukturierte Daten nimmt man aber besser gleich eine Data Class:

data class Profil(val name: String, val alter: Int, val hobbys: List<String>)
 
val profil = Profil("Marcel", 34, listOf("Programmieren", "Lesen", "Gaming"))
println(profil.name) // Marcel

Typen in Funktionen

Parametertypen sind Pflicht, der Rückgabetyp steht hinter der Klammer:

fun berechnePreisMitSteuer(nettoPreis: Double, steuersatz: Double = 0.19): Double {
    return nettoPreis * (1 + steuersatz)
}
 
fun findeBenutzername(id: Int): String? {
    // String? bedeutet: String ODER null
    return if (id == 1) "marcel" else null
}

Default-Parameter wie steuersatz = 0.19 sind eingebaut – kein Überladen nötig.

Was man dafür bekommt:

  • Der Code dokumentiert sich selbst
  • Die IDE kann einem wirklich helfen
  • Wer null zurückgeben kann, muss es im Typ zugeben – und jeder Aufrufer sieht es

Aufgaben

Genug Theorie.

Am meisten hängen bleibt, wenn man es selbst tippt. Kotlin Playground auf und los.

Aufgabe 1: Typen raten

Was gibt Kotlin hier jeweils aus – oder kompiliert es gar nicht? Erst überlegen, dann ausführen.

println(10 / 3)
println(10 / 3.0)
println("5" + 3)
println(5 + "3")
println("abc" == "abc")

Aufgabe 2: Steckbrief

Lege eine data class Profil mit name (String), alter (Int), hobbys (List aus Strings) und beruf (String oder null) an. Erzeuge eine Instanz und gib einen Satz aus wie: Marcel ist 34 und hat 3 Hobbys.

Tipp: hobbys.size zählt die Einträge, String-Templates bauen den Satz.

Aufgabe 3: Null-Safety erleben

Deklariere val beruf: String? = null. Gib die Länge des Strings aus – erst mit ?., dann mit ?: 0 als Fallback. Was passiert, wenn du es mit !! versuchst?

Aufgabe 4: Der Geld-Klassiker

Ein Kunde kauft drei Artikel zu je 0.10 €. Prüfe mit ==, ob die Summe 0.30 ergibt. Falls nicht: Rechne dasselbe noch einmal in Cent als Int und prüfe erneut.

Aufgabe 5: Typ-Detektiv

Schreibe eine Funktion beschreibeTyp(wert: Any?): String, die für einen beliebigen Wert einen Satz zurückgibt, z. B. "Das ist ein Int mit dem Wert 42".

Tipp: wert!!::class.simpleName liefert den Typnamen – oder du löst es elegant mit when und is. Denk an den null-Fall!


Lösungsvorschläge

Erst selbst probieren. Ehrlich. Dann vergleichen.

Lösung zu Aufgabe 1
println(10 / 3)        // 3 – Int / Int bleibt Int, der Rest wird abgeschnitten
println(10 / 3.0)      // 3.3333333333333335 – sobald ein Double dabei ist, wird Double gerechnet
println("5" + 3)       // "53" – String + irgendwas ist String-Verkettung
// println(5 + "3")    // kompiliert nicht! Int + String gibt es in Kotlin nicht
println("abc" == "abc") // true – == vergleicht in Kotlin den Inhalt

Die vierte Zeile ist der Aha-Moment: Was PHP oder JavaScript stillschweigend umwandeln, lehnt Kotlin beim Kompilieren ab.

Lösung zu Aufgabe 2
data class Profil(
    val name: String,
    val alter: Int,
    val hobbys: List<String>,
    val beruf: String?,
)
 
fun main() {
    val profil = Profil(
        name = "Marcel",
        alter = 34,
        hobbys = listOf("Programmieren", "Lesen", "Gaming"),
        beruf = "Software-Entwickler",
    )
 
    println("${profil.name} ist ${profil.alter} und hat ${profil.hobbys.size} Hobbys.")
}
Lösung zu Aufgabe 3
val beruf: String? = null
 
println(beruf?.length)      // null – Safe Call steigt einfach aus
println(beruf?.length ?: 0) // 0 – Elvis liefert den Fallback
// println(beruf!!.length)  // NullPointerException – !! erzwingt den Zugriff
 

!! ist die einzige Stelle, an der Kotlin dich wieder in die Java-Welt lässt. Deshalb: fast nie benutzen.

Lösung zu Aufgabe 4
val summe = 0.10 + 0.10 + 0.10
println(summe == 0.30) // false – Float-Rundungsfehler
println(summe)         // 0.30000000000000004
 
val summeInCent = 10 + 10 + 10
println(summeInCent == 30) // true – deshalb Geld immer in Cent als Int
Lösung zu Aufgabe 5
fun beschreibeTyp(wert: Any?): String {
    return when (wert) {
        null -> "Das ist null – hier ist bewusst kein Wert"
        else -> "Das ist ein ${wert::class.simpleName} mit dem Wert $wert"
    }
}
 
fun main() {
    println(beschreibeTyp(42))     // Das ist ein Int mit dem Wert 42
    println(beschreibeTyp("42"))   // Das ist ein String mit dem Wert 42
    println(beschreibeTyp(true))   // Das ist ein Boolean mit dem Wert true
    println(beschreibeTyp(null))   // Das ist null – hier ist bewusst kein Wert
}

Schön daran: Nach dem null-Zweig im when weiß der Compiler, dass wert nicht mehr null sein kann – Smart Cast. Kein !! nötig.


Zusatz: Die Double-Falle

Und jetzt kommt eine Falle, in die wirklich jeder einmal reinläuft:

println(0.1 + 0.2 == 0.3) // false
println(0.1 + 0.2)        // 0.30000000000000004

Ja, wirklich. false.

Doubles sind intern binär gespeichert und dadurch nicht beliebig genau. Das ist keine Kotlin-Eigenheit – das betrifft praktisch jede Sprache. Genau deshalb rechnet man in echten Projekten Geldbeträge niemals mit Doubles, sondern in Cent als Int/Long – oder mit BigDecimal.

Das ist keine Theorie. Das ist die Sorte Bug, die irgendwann in einer Rechnung landet.


Wie geht es weiter?

Das waren die Bausteine, aus denen jedes Kotlin-Programm besteht.

Im nächsten Teil geht es um Kontrollstrukturen – if als Expression, when, Schleifen. Also darum, wie ein Programm Entscheidungen trifft.

  • 5 Min. LesezeitPHP

    PHP-Grundlagen Teil 2 – Typen in PHP verstehen

    Welche Datentypen es in PHP gibt, wofür man sie wirklich braucht und warum strict_types von Anfang an gesetzt gehört – mit Übungsaufgaben zum Selbermachen.

    • #PHP
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Webentwicklung
    • #Backend
  • 5 Min. LesezeitC#

    C#-Grundlagen Teil 2 – Typen in C# verstehen

    Value Types, Reference Types, Nullable Reference Types und warum decimal für Geld gemacht ist: die Grundlagen des C#-Typsystems – mit Übungsaufgaben zum Selbermachen.

    • #C#
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Backend
  • 5 Min. LesezeitJava

    Java-Grundlagen Teil 2 – Typen in Java verstehen

    Primitives, Wrapper-Klassen, Autoboxing und die ==-Falle bei Strings: Was man über Javas Typsystem von Anfang an wissen sollte – mit Übungsaufgaben zum Selbermachen.

    • #Java
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Backend