Rheinwerk Computing < openbook >

 
Inhaltsverzeichnis
Vorwort
Teil I Grundlagen
1 Android – eine offene, mobile Plattform
2 Hallo Android!
3 Von der Idee zur Veröffentlichung
Teil II Elementare Anwendungsbausteine
4 Wichtige Grundbausteine von Apps
5 Benutzeroberflächen
6 Multitasking
Teil III Gerätefunktionen nutzen
7 Telefonieren und surfen
8 Sensoren, GPS und Bluetooth
Teil IV Dateien und Datenbanken
9 Dateien lesen, schreiben und drucken
10 Datenbanken
Teil V Multimedia und Produktivität
11 Multimedia
12 Kontakte und Organizer
A Einführung in Kotlin
B Jetpack Compose
C Häufig benötigte Codebausteine
D Literaturverzeichnis
E Die Begleitmaterialien
Stichwortverzeichnis

Ihre Meinung?
Spacer
<< zurück
Android 11 von Thomas Künneth
Das Praxisbuch für App-Entwickler
Buch: Android 11

Android 11
Pfeil A Einführung in Kotlin
Pfeil A.1 Überblick
Pfeil A.1.1 Hello, Kotlin
Pfeil A.1.2 Beim Start Argumente übergeben
Pfeil A.2 Datentypen
Pfeil A.2.1 Zahlen
Pfeil A.2.2 Zeichen und Zeichenketten
Pfeil A.2.3 Wahrheitswerte
Pfeil A.2.4 Felder
Pfeil A.3 Programmlogik
Pfeil A.3.1 Funktionen
Pfeil A.3.2 Programmfluss
Pfeil A.3.3 Variablen und Eigenschaften
Pfeil A.4 Null-Sicherheit
Pfeil A.4.1 Nullbare Typen
Pfeil A.4.2 Elvis Operator und unsicherer Zugriff
Pfeil A.5 Objektorientierung
Pfeil A.5.1 Einfache Klassen
Pfeil A.5.2 Sekundäre Konstruktoren und init-Blöcke
Pfeil A.5.3 Gleichheit und Identität
Pfeil A.5.4 Vererbung
Pfeil A.6 Fortgeschrittene Themen
Pfeil A.6.1 Singletons und Companion-Objekte
Pfeil A.6.2 Sichtbarkeit
Pfeil A.6.3 Erweiterungen
Pfeil A.6.4 Kompakter Code
Pfeil A.7 Zusammenfassung
 
Zum Seitenanfang

A.5    Objektorientierung Zur vorigen ÜberschriftZur nächsten Überschrift

Klassen sind die wichtigsten Grundbausteine in der objektorientierten Programmierung. Mit Ihnen werden Daten und Verhalten modelliert. Letzteres steckt in den Methoden und deren wechselseitigen Aufrufen. Die Daten stecken (zur Laufzeit) in Instanzvariablen der Objekte. Vereinfacht ausgedrückt sind Methoden Funktionen im Kontext einer Klasse.

 
Zum Seitenanfang

A.5.1    Einfache Klassen Zur vorigen ÜberschriftZur nächsten Überschrift

Listing A.35 zeigt eine vollständige Klasse in Kotlin. Auf das Schlüsselwort class folgt der Name der Klasse, etwaige Typparameter, das Schlüsselwort constructor und eine Liste von Argumenten. Der sogenannte primäre Konstruktor gehört zum Klassenkopf. Seine Signatur gibt die bevorzugte Instanziierung an. Er kann keinen Code enthalten. Ist dies nötig, werden init-Blöcke verwendet. Dazu komme ich etwas später. Das Schlüsselwort constructor kann entfallen, wenn im Konstruktor keine Annotationen und Zugriffsmodifizierer angegeben werden.

class Person constructor(var name: String, var age: Int) {
}

Listing A.35    Eine vollständige Klasse in Kotlin

Auffällig ist, dass weder Eigenschaften noch Methoden definiert wurden. Aber was tut Person dann? Listing A.36 erzeugt ein Objekt des Typs Person und weist es der Variablen p zu. Danach werden die beiden Eigenschaften name und age mit neuen Werten überschrieben. Der Aufruf von println() führt dann zur Ausgabe von »Max Mustermann ist 42 Jahre alt.«. Ist Ihnen aufgefallen, dass im Gegensatz zu Java und C# kein new für die Instanziierung verwendet wird? Tatsächlich kennt Kotlin dieses Schlüsselwort überhaupt nicht. Auch Swift kommt ohne aus.

fun main() {
val p = Person("Max", 123)
p.name = "Max Mustermann"
p.age = 42
println("${p.name} ist ${p.age} Jahre alt.")
}

Listing A.36    Verwendung der Klasse »Person«

Parameter des primären Konstruktors werden zu Eigenschaften, die gelesen und geschrieben (var) oder nur gelesen (val) werden können. Zugriffe auf Eigenschaften lassen sich mit Sichtbarkeitsmodifikatoren steuern. Wird var bzw. val weggelassen, kann die Variable nur in init-Blöcken verwendet werden.

Datenklassen

Sie können die Klasse Person mit dem Schlüsselwort data vor class zu einer Datenklasse machen, eine der wichtigsten Neuerungen in Kotlin gegenüber Java. Häufig braucht man Klassen, die ausschließlich Datenstrukturen repräsentieren oder als Transferobjekte fungieren. Diese speichern Werte und ermöglichen den Zugriff darauf, enthalten aber keine Fachlogik. Da Java keine echten Eigenschaften mit Getter und Setter auf Sprachebene kennt, müssen Zugriffsmethoden explizit ausprogrammiert werden. Das entfällt in Kotlin. Zusätzlich bezieht der Compiler bei Datenklassen in den Methoden equals(), hashCode() und toString() automatisch alle Eigenschaften mit ein.

Beispielsweise führt println(Person("Max", 123).toString()) bei der Klassendefinition in Listing A.36 zu etwas wie »Person@5a07e868«. Haben Sie mit data die Klasse Person aber zu einer Datenklasse gemacht, erscheint stattdessen »Person(name=Max, age=123)«. Außerdem enthalten Datenklassen eine Funktion zum Kopieren. Mit copy() können gezielt Eigenschaften geändert werden.

 
Zum Seitenanfang

A.5.2    Sekundäre Konstruktoren und init-Blöcke Zur vorigen ÜberschriftZur nächsten Überschrift

Sekundäre Konstruktoren ermöglichen die Instanziierung der Klasse mit anderen Parametern als über den primären Konstruktor. Falls die Klasse einen primären Konstruktor definiert, muss jeder sekundäre Konstruktor diesen aufrufen, entweder direkt oder über einen anderen sekundären Konstruktor. Hierfür wird das Schlüsselwort this verwendet. Listing A.37 zeigt, wie Sie einen sekundären Konstruktor definieren.

fun main() {
val p = Person()
println("${p.age}")
}

class Person(var name: String, var age: Int) {
init {
println("name: $name, age: $age")
}

constructor(): this("???",-1) {
println("Instanziierung über parameterlosen Konstruktor")
}
}

Listing A.37    Verwendung eines sekundären Konstruktors

Auf das Schlüsselwort constructor folgt die Parameterliste. In meinem Beispiel ist sie leer. Ihr folgt ein Doppelpunkt und, sofern nötig, der Aufruf eines anderen Konstruktors – hier der primäre Konstruktor mit den Argumenten "???" und -1. Da der primäre Konstruktor keinen Code enthalten kann, packen Sie Initialisierungsaufgaben in einen oder mehrere init-Blöcke. Während der Instanziierung werden diese in der Reihenfolge, in der sie im Quelltext stehen, abgearbeitet. Eigenschaften, die vor einem init-Block definiert wurden, können darin verwendet werden. Wie das aussehen kann, zeigt Listing A.38.

class InitBlockDemo(name: String) {
val a = name.also(::println)

init {
println("Erster Block: $a")
}

val b = "${a.toUpperCase()}".also(::println)

init {
println("Zweiter Block: $b")
}

constructor() : this("Hallo") {
println("Sekundärer Konstruktor")
}
}

fun main() {
InitBlockDemo()
}

Listing A.38    Gestufte Initialisierung mit init-Blöcken

Die Bildschirmausgaben sind in Abbildung A.4 zu sehen. init-Blöcke werden vor Konstruktor-Code ausgeführt. Der Rumpf meines parameterlosen sekundären Konstruktors wird zuletzt abgearbeitet.

Bildschirmausgaben von

Abbildung A.4    Bildschirmausgaben von Listing A.38

 
Zum Seitenanfang

A.5.3    Gleichheit und Identität Zur vorigen ÜberschriftZur nächsten Überschrift

Sofern keine explizite Elternklasse angegeben wird, leiten Klassen in Kotlin von Any ab. Wie Object in Java enthält die Wurzel des Typsystems die grundlegenden Methoden equals(), hashCode() und toString(). Die ersten beiden werden unter anderem verwendet, um Objekte auf Gleichheit und Identität zu prüfen. toString() liefert eine Darstellung des Objekts als Zeichenkette. Für Any ist dies eine Referenz in der Form »java.lang.Object@5a07e868« (sofern die Erzeugung in einer Java Virtual Machine stattfindet).

Kotlin unterscheidet zwischen struktureller und referenzieller Gleichheit. Die Unterschiede demonstriert Listing A.39. === prüft, ob zwei Referenzen auf dasselbe Objekt verweisen. Da b als neuer String erzeugt wird, ist das nicht der Fall, auch wenn er den gleichen Inhalt wie a hat. == hingegen vergleicht die Zeichen. Diese sind gleich, da b aus einem ByteArray von a erzeugt wurde.

val a = "Hallo Kotlin"
val b = String(a.toByteArray())
// Referenzielle Gleichheit
println("a === b: ${a === b}")
// Strukturelle Gleichheit
println("a == b: ${a == b}")

Listing A.39    Strukturelle und referenzielle Gleichheit

== entspricht equals, Sie könnten also auch ${a.equals(b)} schreiben. Wenn Sie bereits in Java programmiert haben, vergegenwärtigen Sie sich bitte, dass == in Java Objektreferenzen vergleicht und deshalb dem === von Kotlin entspricht. Nur bei primitiven Datentypen verhält sich == in Java so wie in Kotlin.

 
Zum Seitenanfang

A.5.4    Vererbung Zur vorigen ÜberschriftZur nächsten Überschrift

Soll eine Klasse von einer bestimmten Elternklasse erben, wird diese im Klassenkopf nach der Parameterliste des primären Konstruktors in der Form : <Name der Elternklasse> angegeben. Hat die Basisklasse einen primären Konstruktor, muss dieser aufgerufen werden. Das ist in Listing A.40 zu sehen. Im Klassenkopf von B wird mit : A(24) der Konstruktor der Elternklasse A aufgerufen. Wie Java kennt Kotlin, anders als C++, keine Mehrfachvererbung.

fun main() {
A(42).hallo()
B().hallo()
}

open class A(a: Int) {
init {
print("A").also { println(" mit a=$a")}
}

open fun hallo() = println("Hallo")
}

class B : A(24) {
init {
println("B")
}

override fun hallo() = println("Kotlin")
}

Listing A.40    Einfaches Beispiel von Vererbung

Abbildung A.5 zeigt, in welcher Reihenfolge die init-Blöcke der beiden Klassen abgearbeitet werden. »A mit ...« erscheint zweimal. Die erste Ausgabe ergibt sich aus der Zeile A(42) in der Funktion main(). Die zweite ist die Folge des Konstruktoraufrufs A(24) im Klassenkopf von B.

Die Bildschirmausgabe von

Abbildung A.5    Die Bildschirmausgabe von Listing A.40

Ist Ihnen das Schlüsselwort open im Klassenkopf von A aufgefallen? Kotlin-Klassen sind final, können also standardmäßig nicht abgeleitet werden. Ist Vererbung gewünscht, müssen Sie open verwenden. Gleiches gilt für Methoden. Nur die Klasse zu öffnen, reicht nicht. Gleichzeitig müssen abgeleitete Klassen bei Methoden, die sie überschreiben, das Schlüsselwort override verwenden. Sie können Methoden der Elternklasse wie in Java mit super. aufrufen.

Abstrakte Klassen und Methoden müssen mit dem Schlüsselwort abstract versehen werden. Im Klassenkopf kann open entfallen. Die ableitende Klasse muss überschriebene Methoden aber mit override kennzeichnen (Listing A.41).

fun main(args: Array<String>) {
B().hallo("Kotlin")
}

abstract class A {
abstract fun hallo(s: String)
}

class B : A() {
override fun hallo(s: String) = println("Hallo, $s")
}

Listing A.41    Abstrakte Klassen und Methoden

Darüber hinaus gibt es noch eine interessante Verwendung von abstract. Anders als beispielsweise in Java können Sie abstrakte Eigenschaften definieren. Wie, sehen Sie in Listing A.42. Die abstrakte Klasse A ist mit T typisiert. Diesen Typ verwendet die abstrakte Eigenschaft value. var bedeutet, dass Werte gesetzt und gelesen werden können.

fun main() {
println(B().value)
println(C().value)
}

abstract class A<T> {
abstract var value: T
}

class B : A<String>() {
override var value = "Hallo"
}

class C : A<Int>() {
override var value = 42
}

Listing A.42    Abstrakte Eigenschaften

Die Klasse B leitet von A<String> ab, value nimmt deshalb Zeichenketten auf. C typisiert A mit Int, deshalb enthält value in C ganze Zahlen.

Interfaces werden mit dem Schlüsselwort interface definiert. Klassen, die es implementieren möchten, verwenden wie bei der Ableitung den Doppelpunkt (Listing A.43). Interfaces können neben den klassischen Methodendeklarationen auch vollständige Implementierungen anbieten. Auch die Definition von Eigenschaften ist möglich.

fun main() {
val greeter = ConsoleGreeter()
greeter.sayHello(greeter.defaultName)
}

interface Greeter {
val defaultName: String
get() = "world"

fun sayHello(name: String)

fun console(s: String) {
println(s)
}
}

class ConsoleGreeter: Greeter {
override val defaultName = "Kotlin"
override fun sayHello(name: String) = console("Hallo, $name")
}

Listing A.43    Interfaces in Kotlin

Allerdings können Sie Eigenschaften nicht direkt initialisieren (val defaultName = "world"). Das führt zur Fehlermeldung »Property initializers are not allowed in interfaces«. Sie müssen wie gezeigt den Wert in einem Getter setzen. Sollen die implementierenden Klassen den Wert vorgeben, muss die Eigenschaft abstract sein.

In Java ist es – anders als beispielsweise in C# – mit anonymen inneren Klassen einfach, ein Interface zu implementieren, ohne extra eine Klasse definieren zu müssen. Das geht auch in Kotlin. Die Vorgehensweise zeigt Listing A.44.

val greeter = object: Greeter {
override val defaultName = "Kotlin"
override fun sayHello(name: String) = console("Hallo, $name")
}
greeter.sayHello(greeter.defaultName)

Listing A.44    Direkte Implementierung eines Interfaces

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
Zur Rheinwerk-Konferenz für Kotlin
 Buchempfehlungen
Zum Rheinwerk-Shop: Kotlin

Kotlin


Zum Rheinwerk-Shop: Praxisbuch Usability und UX

Praxisbuch Usability und UX


Zum Rheinwerk-Shop: Flutter und Dart

Flutter und Dart


Zum Rheinwerk-Shop: App-Design

App-Design


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2023
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 
[Rheinwerk Computing]

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern