A.6 Fortgeschrittene Themen 

In diesem Abschnitt stelle ich Ihnen fortgeschrittene Themen und Kotlin-Spezialitäten vor. Ich habe sie in diese Einführung gepackt, weil Sie beim Schreiben von Apps auf jeden Fall darauf stoßen werden.
A.6.1 Singletons und Companion-Objekte 

Kotlin kennt im Gegensatz zu Java nicht das Schlüsselwort static. Variablen oder Funktionen ohne Bezug zu einer Klasse oder Instanz werden einfach auf Wurzel- oder Paketebene definiert, also in einer beliebigen .kt-Quelltextdatei (vor oder nach einer Klassendefinition). Aber wie geht man vor, wenn man auf private Daten oder Methoden einer Klasse zugreifen muss? Denken Sie an Zufallszahlengeneratoren. Die Funktion nextInt() könnte eigentlich prima ohne Klassen- oder Objektbezug funktionieren, aber wo wird dann der Zustand gehalten?
fun main() {
for (i in 1..3) {
println(NotSoRandom.nextInt())
}
}
object NotSoRandom {
private var a = 42
fun nextInt(): Int {
a *= 2
return a
}
}
Listing A.45 Singletons in Kotlin
Zugegeben, der Zustand des Singletons (eine Klasse mit genau einer Instanz) NotSoRandom in Listing A.45 ist überschaubar: eine Variable. Aber sie – und genau das möchte ich Ihnen mit dem Beispiel zeigen – kann sich bei mehreren Aufrufen ändern, ohne dass sich der Aufrufer einen Zustand merken und zum Beispiel als zusätzlichen Methodenparameter übergeben muss. Die Initialisierung von Singletons ist Thread-sicher. Sie werden wie Klassen definiert, nur verwenden Sie anstelle von class das Schlüsselwort object. Sie können dann einfach über den Namen auf Eigenschaften und Funktionen des Singletons zugreifen. Wenn es innerhalb einer Klasse verwendet wird, wirkt der benannte Zugriff aber unschön:
fun main() {
for (i in 1..6) {
println(NotSoRandom2().nextInt())
}
}
class NotSoRandom2 {
object Magic {
private val l = listOf(1, 2, 3)
private var pos = 0
fun next(): Int {
if (pos >= l.size)
pos = 0
return l[pos++]
}
}
fun nextInt() = Magic.next()
}
Listing A.46 Beispiel für unschönen benannten Zugriff
In Listing A.46 wird mit Magic.nextInt() die nächste (nicht zufällige) Zahl ermittelt. Da das Singleton Magic direkt in der Klasse NotSoRandom2 verwendet wird, stört die notwendige Nennung des Namens, zumal Kotlin-Code sonst sehr kompakt ist. Glücklicherweise lässt sich das Problem sehr leicht beheben. Machen Sie aus dem Singleton einfach ein sogenanntes Companion-Objekt, indem Sie vor object das Schlüsselwort companion setzen. Companion-Objekte können einen Namen haben, Magic kann also stehen bleiben. Sie können das Wort aber auch entfernen. In diesem Fall wäre ein benannter Zugriff mit dem Namen der umschließenden Klasse (NotSoRandom2) möglich. Methodenaufrufe – und das ist ja der Sinn davon – brauchen aber kein Präfix mehr, Magic. kann also entfallen.
[»] Hinweis
Member des Companion-Objekts sind normale Instanz-Member der umschließenden Klasse. Soll das Programm in einer Java Virtual Machine ausgeführt werden, kann mit der Annotation @JvmStatic ein statischer Member erzeugt werden.
Klassen, Objekte, Interfaces, Konstruktoren, Funktionen, Eigenschaften und deren Setter können in ihrer Sichtbarkeit eingeschränkt werden. Welche Stufen es gibt und wie sie sich auswirken, zeige ich Ihnen im folgenden Abschnitt.
A.6.2 Sichtbarkeit 

Kotlin kennt die Sichtbarkeitsstufen private, protected, internal und public. Wird kein Modifizierer angegeben, gilt public. Wenn Sie die Sichtbarkeit einschränken möchten, müssen Sie dies explizit angeben.
Schlüsselwort |
Bedeutung |
---|---|
public |
Zugriff nicht eingeschränkt |
private |
|
protected |
|
internal |
|
Tabelle A.3 Zugriffsmodifizierer und ihre Bedeutung
Module bestehen aus zusammen übersetzten Kotlin-Dateien. Mögliche Quellen sind IntelliJ IDEA-Module, Maven-Projekte, Gradle source sets sowie mehrere Quelltextdateien, die in einem <kotlinc> Ant Task übersetzt wurden.
A.6.3 Erweiterungen 

Mit Erweiterungen können Sie Klassen um neue Funktionen erweitern, ohne von ihr abzuleiten oder auf klassische Entwurfsmuster (beispielsweise Decorator) zurückzugreifen. Das ist sehr praktisch, wenn der Quelltext einer Klasse nicht zur Verfügung steht, weil sie zu einer vorübersetzten Bibliothek gehört. Listing A.47 zeigt eine Erweiterung für Strings.
fun main() {
val s = "AB"
println(s.toAsterisk())
}
fun String.toAsterisk(): String {
return String(CharArray(this.length, { '*' }))
}
Listing A.47 Eine Erweiterungsfunktion für die Klasse »String«
this im Rumpf der Funktion ist optional. Erweiterungen werden statisch aufgelöst. Sie modifizieren Klassen nicht. Die neuen Funktionen werden nur über die Punkt-Notation aufrufbar. Der Receivertyp kann bei Bedarf nullbar oder generisch sein. Das ist in Listing A.48 zu sehen. Der Cast (this as Any) ist nötig, weil sonst die Ermittlung des Klassennamens nicht erlaubt ist.
fun main() {
"123".info()
123.info()
null.info()
}
fun <T> T?.info() {
println(
if (this == null)
"ist null"
else
(this as Any)::class.simpleName
)
}
Listing A.48 Erweiterungsfunktion mit nullbarem und generischem Receiver
Die zu erweiternde Klasse wird zur Compilezeit bestimmt. Abgeleitete Klassen werden deshalb anders behandelt, als man vielleicht erwarten würde. In Listing A.49 werden zwei Klassen definiert. B leitet von A ab. Beide erhalten eine Erweiterungsfunktion getName(). Diese liefern je nach Klasse "A" oder "B". Die Funktion printClassName() erhält ein Objekt des Typs A und ruft darauf die Erweiterungsfunktion getName() auf. Wenn eine B-Instanz übergeben wird, müsste demnach "B" zurückgegeben werden, oder?
fun main() {
printClassName(A())
printClassName(B())
}
open class A
class B: A()
fun A.getName() = "A"
fun B.getName() = "B"
fun printClassName(s: A) {
println(s.getName())
}
Listing A.49 Ermittlung der zu erweiternden Klasse zur Compilezeit
Der Typ des Receivers wird zur Compilezeit bestimmt. Das ist A, weshalb unabhängig vom Typ zur Laufzeit immer A.getName() aufgerufen wird.
A.6.4 Kompakter Code 

Kotlin-Code soll kompakt, aber dennoch gut lesbar sein. Deshalb können Strukturelemente wie { } und ( ) unter bestimmten Umständen weggelassen werden. Beispielsweise würde man
(1..3).forEach( { i -> println(i * i)})
zu
(1..3).forEach { println(it * it) }
verkürzen.
Praktisch ist auch, dass Sie nicht verwendete Parameter in Lambda-Ausdrücken mit _ als wird nicht verwendet kennzeichnen können (Listing A.50).
Listing A.50 Nicht verwendeter Parameter in einem Lambda-Ausdruck
Das wird üblicherweise noch weiter reduziert auf:
(1..3).forEach { count++ }
Es fällt also nicht nur der nicht verwendete Parameter weg, sondern auch der Lambdapfeil -> und die forEach-Funktionsklammern.