Kotlin
Kotlin-Grundlagen Teil 2 – Typen in Kotlin verstehen
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" // CompilerfehlerRegel 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 wurdeExplizit 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 = 2026Ganze 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.14159Wichtig: 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 = falseZwei 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 ZeichenKeine 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 nichtKein 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 ObjekteFü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 seinUnd 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 kommtnullraus?:– 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"]) // MarcelWichtig: 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) // MarcelTypen 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
nullzurü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 InhaltDie 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 IntLö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.30000000000000004Ja, 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.