s n h m r u

Minimallogo des digitalen Schulbuchs inf-schule.de. Schriftzug in Zustandsübergangsdiagramm eines endlichen Automaten.

s n h m r u
i

Fachkonzept - Objekte als Werte

Objekte sind Werte

In den bisherigen Projekten waren die Daten, mit denen wir gearbeitet haben, meistens einfache Werte: Zahlen vom Typ Int, Wahrheitswerte vom Typ Boolean oder Zeichenketten vom Typ String. Solche Werte haben wir als Attribute gespeichert, als Parameter an Methoden übergeben und als Rückgabewerte aus Methoden zurückbekommen.

Tatsächlich gibt es in Kotlin aber gar keinen grundsätzlichen Unterschied zwischen einer Zahl wie 5 und einem selbst geschriebenen Objekt. Auch 5 ist ein Objekt – und ebenso ist ein Objekt einer eigenen Klasse ein Wert. Überall dort, wo bisher ein einfacher Wert stehen durfte, darf deshalb auch ein beliebiges Objekt stehen.

Wir betrachten das an einem Beispiel rund um Bücher. Ein einzelnes Buch sieht so aus:

class Buch(val titel: String, val autor: String) {
    fun druckeInfo() {
        println("„$titel“ von $autor")
    }
}

In Kotlin sind alle Werte Objekte. Deshalb kann man Objekte überall dort verwenden, wo man auch einfache Werte verwendet: als Attribut, als Parameter und als Rückgabewert einer Methode.

Objekte als Rückgabewerte

Eine Methode kann nicht nur eine Zahl oder eine Zeichenkette zurückgeben, sondern ein beliebiges Objekt. Ein Verlag kann zum Beispiel ein neues Buch herstellen und dieses als Objekt zurückgeben. Im Rückgabetyp steht dann einfach der Name der Klasse:

class Verlag(val name: String) {
    fun veroeffentliche(titel: String, autor: String): Buch {
        return Buch(titel, autor)
    }
}

Das zurückgegebene Objekt kann anschließend genauso weiterverwendet werden wie jeder andere Wert. Insbesondere kann man an ihm sofort wieder eine Methode aufrufen:

class Buch(val titel: String, val autor: String) {
    fun druckeInfo() {
        println("„$titel“ von $autor")
    }
}

class Verlag(val name: String) {
    fun veroeffentliche(titel: String, autor: String): Buch {
        return Buch(titel, autor)
    }
}

fun main() {
    val verlag = Verlag("Beispiel-Verlag")
    val buch = verlag.veroeffentliche("Emil und die Detektive", "Erich Kästner")
    buch.druckeInfo()

    // oder direkt am Rückgabewert weiterarbeiten:
    verlag.veroeffentliche("Pünktchen und Anton", "Erich Kästner").druckeInfo()
}

Objekte als Parameter

Genauso können Objekte als Parameter an Methoden übergeben werden. Eine Vitrine stellt jeweils ein Buch aus. Mit der Methode stelleAus übergibt man ihr ein ganzes Buch-Objekt:

class Vitrine {
    var ausgestelltesBuch: Buch? = null

    fun stelleAus(buch: Buch) {
        ausgestelltesBuch = buch
    }
}

Beim Aufruf übergibt man ein konkretes Buch-Objekt. Dieses kann z.B. zuvor vom Verlag erzeugt worden sein:

val vitrine = Vitrine()
vitrine.stelleAus(buch)               // ein Objekt wird als Parameter übergeben

Wenn es kein Objekt gibt

Beim Arbeiten mit Objekten taucht eine neue Situation auf, die es bei den bisherigen Aufgaben so noch nicht gab: Manchmal gibt es gar kein Objekt. In einer frisch aufgestellten Vitrine ist zum Beispiel noch nichts ausgestellt. Was soll dann in der Vitrine stehen, wenn man nach dem ausgestellten Buch fragt?

Damit man solche Fälle sauber behandeln kann, unterscheidet Kotlin streng zwischen Typen, die immer ein Objekt enthalten, und Typen, die auch nichts enthalten dürfen. Dieses „nichts“ heißt in Kotlin null.

Ein Datentyp mit angehängtem Fragezeichen wie Buch? heißt nullbarer Typ (englisch nullable type). Man kann ihn als „Buch oder nichts“ lesen. Ein Wert dieses Typs ist also entweder ein echtes Buch-Objekt oder null.

Ein Datentyp ohne Fragezeichen wie Buch enthält dagegen garantiert immer ein echtes Objekt und niemals null.

Deshalb hat das Attribut ausgestelltesBuch den Typ Buch? und beginnt mit dem Wert null: Solange nichts ausgestellt wurde, gibt es kein Buch. Eine Methode, die das ausgestellte Buch zurückgibt, hat dann ebenfalls den Rückgabetyp Buch?:

fun aktuellesBuch(): Buch? {
    return ausgestelltesBuch     // Buch oder null
}

Das Problem mit null

An einem Objekt, das gar nicht da ist, kann man keine Methode aufrufen. Der Versuch, an null die Methode druckeInfo() aufzurufen, ergibt keinen Sinn – schließlich gibt es kein Buch, dessen Informationen man drucken könnte. Kotlin verhindert deshalb von vornherein, dass man an einem Wert vom Typ Buch? einfach so eine Methode aufruft:

class Buch(val titel: String, val autor: String) {
    fun druckeInfo() {
        println("„$titel“ von $autor")
    }
}

class Vitrine {
    var ausgestelltesBuch: Buch? = null

    fun aktuellesBuch(): Buch? {
        return ausgestelltesBuch
    }
}

fun main() {
    val vitrine = Vitrine()
    val b: Buch? = vitrine.aktuellesBuch()
    b.druckeInfo()      // Fehler! b könnte null sein
}

Schon der Compiler weist diesen Code zurück, bevor das Programm überhaupt läuft. Das ist ein großer Vorteil: Viele Programmierfehler, die in anderen Sprachen erst zur Laufzeit zu Abstürzen führen, fallen in Kotlin bereits beim Schreiben des Programms auf. Bevor man an einem nullbaren Wert eine Methode aufrufen darf, muss man also erst den Fall null behandeln.

Umgang mit nullbaren Werten

Es gibt mehrere Möglichkeiten, mit einem Wert vom Typ Buch? umzugehen. Alle haben gemeinsam, dass sie den Fall null ausdrücklich berücksichtigen.

Abfrage mit if

Die anschaulichste Möglichkeit ist, mit einer if-Abfrage zu prüfen, ob der Wert ungleich null ist. Innerhalb des if-Blocks weiß Kotlin dann, dass es sich um ein echtes Buch handelt, und der Methodenaufruf ist erlaubt:

val b: Buch? = vitrine.aktuellesBuch()
if (b != null) {
    b.druckeInfo()           // hier ist b garantiert ein echtes Buch
} else {
    println("Die Vitrine ist leer")
}

Sicherer Aufruf mit ?.

Oft möchte man eine Methode nur dann aufrufen, wenn das Objekt vorhanden ist, und sonst gar nichts tun. Dafür gibt es den sicheren Aufruf (englisch safe call) mit ?.. Ist der Wert null, wird der Aufruf einfach übersprungen und es passiert nichts.

val b: Buch? = vitrine.aktuellesBuch()
b?.druckeInfo()      // druckt nur, wenn b nicht null ist

Ersatzwert mit dem Elvis-Operator ?:

Manchmal möchte man für den Fall null einen Ersatzwert oder eine Ausweichaktion angeben. Das leistet der Elvis-Operator ?:. Steht links davon null, wird der Ausdruck rechts davon verwendet.

class Buch(val titel: String, val autor: String) {
    fun druckeInfo() {
        println("„$titel“ von $autor")
    }
}

class Vitrine {
    var ausgestelltesBuch: Buch? = null

    fun stelleAus(buch: Buch) {
        ausgestelltesBuch = buch
    }

    fun aktuellesBuch(): Buch? {
        return ausgestelltesBuch
    }
}

fun main() {
    val vitrine = Vitrine()

    // vitrine.stelleAus(Buch("Emil und die Detektive", "Erich Kästner"))

    vitrine.aktuellesBuch()?.druckeInfo() ?: println("Die Vitrine ist leer")
}

Hier werden die Informationen des ausgestellten Buchs gedruckt, falls eines vorhanden ist – andernfalls erscheint die Meldung „Die Vitrine ist leer“. Der Name „Elvis-Operator“ ist übrigens ein kleiner Scherz: Dreht man ?: um 90 Grad, erkennt man die Augen und die Haartolle von Elvis Presley.

Ein nullbarer Wert (Typ mit ?) muss behandelt werden, bevor man eine Methode an ihm aufruft. Dafür gibt es drei häufige Wege: die Abfrage mit if (... != null), den sicheren Aufruf ?. und den Elvis-Operator ?: für einen Ersatzwert.

Suche

v
100.123.4.3 Fachkonzept - Objekte als Werte
Kopieren durch Anklicken

Rückmeldung geben