Variablen, Typen & Null Safety
val und var, Kotlins Typen und das Herzstueck: Null Safety mit ?, !!, ?: und let. Nie wieder NullPointerException.
Inhaltsverzeichnis
Variablen, Typen & Null Safety
Kotlins Typsystem ist streng wie Java, aber wesentlich angenehmer zu nutzen. Der groesste Unterschied: Null Safety.
val und var
val name = "Anna" // Konstante (kann nicht neu zugewiesen werden)
var alter = 28 // Variable (kann geaendert werden)
alter = 29 // OK
// name = "Max" // FEHLER - val ist read-only
Faustregel: Nimm val, bis du wirklich var brauchst. Das macht deinen Code vorhersehbarer.
Typ-Inferenz
val name = "Anna" // String
val alter = 28 // Int
val pi = 3.14 // Double
val aktiv = true // Boolean
Mit explizitem Typ:
val name: String = "Anna"
val alter: Int = 28
val pi: Double = 3.14
Primitive Typen
Kotlin hat keine primitiven Typen im Java-Sinn - alles sind Objekte. Aber der Compiler optimiert das:
| Typ | Beschreibung |
|---|---|
Int | 32-Bit Ganzzahl |
Long | 64-Bit Ganzzahl |
Short | 16-Bit |
Byte | 8-Bit |
Double | 64-Bit Fliesskomma |
Float | 32-Bit Fliesskomma |
Boolean | true / false |
Char | Einzelnes Unicode-Zeichen |
String | Zeichenkette |
Zahlen-Literale
val i = 42 // Int
val l = 42L // Long (Suffix L)
val d = 3.14 // Double
val f = 3.14f // Float (Suffix f)
val hex = 0xFF // 255
val bin = 0b1010 // 10
val gross = 1_000_000 // Unterstriche als Lesehilfe
Keine impliziten Konvertierungen
val i = 5
val l: Long = i // FEHLER
val l: Long = i.toLong() // OK
Das wirkt streng, verhindert aber viele subtile Bugs.
Strings
val name = "Anna"
val laenge = name.length // 4
val gross = name.uppercase() // "ANNA"
val ersterBuchstabe = name[0] // 'A'
// Mehrzeilig mit Raw Strings
val sql = """
SELECT *
FROM nutzer
WHERE aktiv = true
""".trimIndent()
Char
val buchstabe: Char = 'A'
val ziffer = '7'
Null Safety
Hier wirdโs spannend - Kotlins groesster Vorteil gegenueber Java.
Standardmaessig nicht-null
var name: String = "Anna"
// name = null // FEHLER - String kann nicht null sein
Variablen koennen nur null sein, wenn du das explizit zulaesst.
Nullable Types mit ?
var name: String? = "Anna"
name = null // OK, weil String? nullable ist
String? ist ein Typ, der entweder einen String enthaelt oder null ist.
Operationen auf Nullables
val name: String? = "Anna"
// val l = name.length // FEHLER - koennte null sein
Kotlin zwingt dich, mit der null-Moeglichkeit umzugehen:
1. Safe Call ?.
val l: Int? = name?.length // wenn name null ist, ist l null
2. Elvis-Operator ?:
Liefert einen Default, wenn links null ist:
val l = name?.length ?: 0 // 0 falls name oder length null
val sicherName = name ?: "Anonym"
3. !!-Operator
Behauptet: โDas ist definitiv nicht nullโ - wirft NPE, wenn doch:
val l = name!!.length // crasht wenn name null
Nutze nur, wenn du 100% sicher bist. Meistens ist Elvis oder ?. besser.
4. if-Check
if (name != null) {
println(name.length) // Smart Cast - Compiler weiss: nicht null
}
Smart Casts sind elegant: Nach einem != null-Check behandelt Kotlin die Variable automatisch als nicht-null.
5. let mit Safe Call
Ein sehr beliebtes Muster:
name?.let { n ->
println("Hallo, $n!")
}
let wird nur ausgefuehrt, wenn name nicht null ist.
Type Casting
is und Smart Cast
fun verarbeite(wert: Any) {
if (wert is String) {
println("String: ${wert.length}") // Smart Cast zu String
} else if (wert is Int) {
println("Int: $wert")
}
}
as und as?
val obj: Any = "Hallo"
val s1 = obj as String // wirft Exception wenn nicht String
val s2 = obj as? String // liefert null wenn nicht String
Collections
Unveraenderbare (bevorzugt)
val namen = listOf("Max", "Anna", "Leo") // List<String>
val alter = mapOf("Anna" to 28, "Max" to 34) // Map<String, Int>
val farben = setOf("Rot", "Gruen", "Blau") // Set<String>
// namen.add("Tim") // FEHLER - read-only
Veraenderbare
val namen = mutableListOf("Max", "Anna")
namen.add("Leo") // OK
namen.removeAt(0)
val alter = mutableMapOf<String, Int>()
alter["Anna"] = 28
val farben = mutableSetOf<String>()
farben += "Rot"
Arrays
val zahlen = arrayOf(1, 2, 3)
val leer = IntArray(5) // [0, 0, 0, 0, 0]
Im Alltag nutzt du eher List als Array.
Ranges
val r = 1..10 // 1 bis 10 inklusiv
val exklusiv = 1 until 10 // 1 bis 9
val schritte = 1..10 step 2 // 1, 3, 5, 7, 9
val runter = 10 downTo 1 // 10, 9, 8, ..., 1
for (i in 1..5) println(i)
Typ-Konvertierung in Strings
val s = "42"
val z = s.toInt() // wirft Exception bei Fehler
val z2 = s.toIntOrNull() // Int? - null bei Fehler
val d = "3.14".toDoubleOrNull() ?: 0.0
toIntOrNull / toDoubleOrNull sind in der Praxis fast immer die bessere Wahl.
Zusammenfassung
valfuer read-only,varfuer veraenderbar - bevorzugeval- Typ-Inferenz oder explizite Typen
- Null Safety macht null-Moeglichkeit im Typ sichtbar (
String?) - Safe Call
?., Elvis?:, Smart Cast,let { ... }sind die Werkzeuge !!nur im Notfall - meistens gibt es bessereslistOf,mapOf,setOffuer unveraenderbar -mutableListOfetc. fuer veraenderbar
Im naechsten Kapitel: Kontrollstrukturen und das beeindruckende when.