Errata Kotlin (1. Aufl. 2020)

Diese Seite enthält Updates und Errata zum Buch »Kotlin« (1. Aufl. 2020). Parallel dazu veröffentliche ich in unregelmäßiger Reihenfolge im Blog dieser Website Update-Artikel, die sich auf spezifische Details des Buchs beziehen. Einen Überblick über alle bisher erschienen Artikel finden Sie hier:

<https://kofler.info/tag/kotlin-updates>

S. 49 (Modulo-Operator)

Auf S. 49 hat sich ein fehlerhaftes Beispiel eingeschlichen, 20.1 &amp; 6. Gemeint war 20.1 % 6, das Ergebnis ist 2,1.

S. 57 (Nullable)

Die letzte Zeile im Code am Ende der Seite lautet var d = a!!. Gemeint war aber var d = b!!. Da b den Zustand null hat, wird durch die erzwungene Auswertung von b ein Fehler ausgelöst.

S. 70 (Tabelle »Besondere Klassen und Objekte«)

In der ersten Zeile beziehe ich mich auf object in Java. Da Object (ein wenig verwirrend) eine Klasse ist, muss das Schlüsselwort groß geschrieben werden (also Object in Java).

S. 80/81 (Die Klasse Char)

In den Listings habe ich die Variable c zweimal in Java-Schreibweise mit char deklariert. Richtig ist in beiden Fällen var c='A'. Der Kotlin-Compiler wählt automatisch den richtigen Datentyp Char.

S. 123 (Listenelemente verändern)

Im Beispielcode sind in den Kommentaren die Ausgaben von println fehlerhaft dargestellt. Korrekt sehen die beiden Listings so aus:

val lst = MutableList(10) { it * it } // [0, 1, 4, 9, 16, ...]
// jedes Listenelement um 1 vergrößern
for (i in 0 until lst.size)
lst[i]++
println(lst) // [1, 2, 5, ..., 82] <-----
// jedes Listenelement nochmals um 1 vergrößern
for (i in lst.indices)
lst[i]++
println(lst) // [2, 3, 6, ..., 83] <---- ``` ##### S. 126 (binarySearch) Wenn `binarySearch` das Element nicht findet, liefert die Funktion eine negative Zahl zurück. Diese gibt den invertierten Einfügepunkt an, der sich als `-insertPoint - 1` errechnet. (Der Einfügepunkt ist die Stelle, an der das gesuchte Element in die sortierte Liste eingefügt werden müsste.) Um das nächst größere Element aus der Liste auszulesen, müssen Sie wie folgt vorgehen: ```kotlin val sortedNumbers = List(100) { it * 7 } // [0, 7, 14, ..., 693] val pos = sortedNumbers.binarySearch(255) if (pos&gt;0)
println("255 ist an der Position $pos in der Liste enthalten")
else {
println("255 ist nicht enthalten. Die nächstgrößere Zahl")
println("lautet ${sortedNumbers[-(pos+1)]}.") // &lt;-- KORREKTUR
}
// Ausgabe: 255 ist nicht enthalten. Die nächstgrößere Zahl
// lautet 259. &lt;-- KORREKTUR
S. 144 (magisches Quadrat)

Beim Spaltentest sind die Variablen in den beiden Schleifen vertauscht. Korrekt sieht der Code so aus:

// Spalten testen
for (col in 0 until n) { // korrigiert, col statt row
var sum = 0
for (row in 0 until n) // korrigiert row statt col
sum += quad[row][col]
if (sum != magicNr)
return false
}
S. 145 (Mehrdimensionale Arrays)

Um ein dreidimensionales Array aus 10x10x10 Double-Zahlen zu erzeugen, gehen Sie so vor:

val dd = Array(10) { Array(10) { DoubleArray(10) } }
S. 155 (Kasten while versus do-while)

Im ersten Absatz des Kastens erkläre ich (korrekt) den Unterschied zwischen while und do-while, nur um die beiden Konstruktionen im zweiten Absatz zu vertauschen. Korrekt lautet der zweite Absatz des Kastens so:

Die beiden obigen Beispiele scheinen sich gleich zu verhalten. Wenn Sie aber als Startwert i = 200000 verwenden, dann wird die do-while-Schleife dennoch einmal durchlaufen (Ausgabe 400000), während die while-Schleife nicht ausgeführt wird.

S. 180 (Pfadsuche)

Im letzten Absatz habe ich MutableList&lt;List&gt; und List&lt;MutableList&gt; durcheinandergebracht. Richtig liest sich der Absatz so:

Vielleicht irritiert Sie, dass der Parameter von printMaze als List&lt;List&gt; deklariert ist, obwohl die ursprüngliche maze-Variable den Typ List&lt;MutableList&gt; hat. Das ist zulässig, weil die Liste innerhalb der Funktion nicht verändert wird und List die Basisklasse für MutableList ist. (Umgekehrt können Sie eine Liste mit dem Datentyp List allerdings nicht an eine Funktion mit dem Parameter MutableList übergeben.)

S. 185 (Code depth-search)

In der 9. Zeile des Codes muss es maze[start.row][start.col] = '.' heißen, d.h. im betroffenen Element muss ein Punkt, kein Leerzeichen gespeichert werden. (In den Beispieldateien zum Buch ist der Code korrekt.)

maze[start.row][start.col] = '.' // Startpunkt als
// ^ bearbeitet markieren
S. 195 (Lambda-Ausdrücke)

In der Einführung zu Lambda-Ausdrücken ist die letzte Zeile der erstes Listing falsch. Die als Lambda-Ausdruck nachgestellte Funktion ist nicht einfach quad, sondern muss quad(it) lauten. Also:

val quad: (Int) -> Int = { x -> x * x }
val lst = listOf(1, 2, 3, 4)
val newlst1 = lst.map(quad)
val newlst2 = lst.map { quad(it) } // gleichwertig
S. 231 (Kurzschreibweisen zur Deklaration von Klassen)

Beim zweiten Beispiel erläutere ich kurz den Einsatz von Datenklassen. Allerdings habe ich im nachfolgenden einzeiligen Beispiel das Schlüsselwort data vergessen. Korrekt sieht das so aus:

data class Person(val id: Int, val name: String, val mail: String)
````

##### S. 267 (Enumerationen wie Klassen verwenden)

Damit der Code des Enumerationsbeispiels zur Fortsetzung auf S. 268 passt, muss in der zweite Zeile die Initialisierung von `StateB` korrigiert werden:

enum class FunnyEnum(val x: Int) : MyInterface {
StateA(12), StateB(15), StateC(34);
// ^^


##### S. 287 (Die component-Methoden) Es handelt sich nicht um eine <del>Destructing</del>, sondern um eine *Destructuring Declaration*. ##### S. 297 (Beispiel comparable-Schnittstelle) Im Listing ist ein `return` zu viel. Der Code für `compareTo` muss so aussehen:

override fun compareTo(other: Rectangle) =
this.getArea().compareTo(other.getArea())


##### S. 329 (Reflexion) Der einzige Supertyp von `Person::class` ist `kotlin.Any`.

val kc = Person::class // kc hat den Typ KClass …
println(kc.allSupertypes) // [class kotlin.Any]


Die im Buch irrtümlich angegebenen Supertypen beziehen sich auf die `Employee`-Klasse, also:

println(Employee::class.allSuperclasses) // [class Person, class kotlin.Any, class TeamLeader]


##### S. 339 (Sternprojektion) Beim Listing im Buch auf S. 339 mitte ist `cnt++` nicht richtig eingerückt. Korrekt sieht das Listing so aus:

fun Iterable<*>.countNotNull(): Int {
var cnt = 0
for (itm in this)
if (itm != null)
cnt++ // <–
return cnt
}


##### S. 350 (Type-safe Builder) Das Listing, das am Seitenende beginnt, befindet sich in der Klasse `Sample2` des Beispielprojekts `type-safe` (nicht in `Sample1`). ##### S. 366 (Eigene Exception-Klassen) Das Listing am Seitenanfang sollte so aussehen:

class DbException(
msg: String
val connectionData: String? = null) : Exception(msg)


##### S. 454 (match) Die auf S. 454 kurz erwähnte Methode `match` verwendet bei SQLite oder H2 im SQL-Code einen Vergleich mit `LIKE`. Bei MySQL/MariaDB führt `match` dagegen eine Volltextsuche durch. Das hat zwei Konsequenzen: Zum einen müssen Sie sicherstellen, dass die Tabelle überhaupt einen Volltextindex für die entsprechende Spalte hat. Das müssen Sie mit `exec` bewerkstelligen, z.B. so:

transaction {
try {
exec(„ALTER TABLE Books ADD FULLTEXT title_ft (title)“)
} catch(e: Exception) { }
}


`try` verhindert, dass es zu einem Fehler kommt, wenn der Index schon existiert. Zum anderen funktioniert `match` jetzt anders. Eines der angegebenen Wörter muss 1:1 in der Spalte enthalten sein. Die Syntax `%muster%` funktioniert nicht. `Books.title.match("Handbuch Linux")` führt zu folgenden SQL-Code:

SELECT Books.id, Books.title, Books.publyear, Books.publId
FROM Books
WHERE MATCH(Books.title) AGAINST (‚Handbuch Linux‘ IN BOOLEAN MODE)


Gefunden werden so Bücher, deren Titel wahlweise die Suchbegriffe *Handbuch* oder *Linux* enthält. ##### S. 455 (smaller) Um Datensätze zu selektieren, die kleiner als ein Grenzwert sind, müssen Sie `less` verwenden, nicht `smaller`. ##### S. 465 (Suchausdruck für select / LIKE) Der LIKE-Platzhalter für ein beliebiges Zeichen ist `_` (nicht `?` wie im Buch angegeben). ##### S. 557 (Android Studio: Datei zu den Assets hinzufügen) Auf S. 557 habe ich beklagt, dass es unmöglich ist, per Drag&amp;Drop eine Datei zu den Assets hinzuzufügen. Es gibt aber einen alternativen Weg: Sie wählen die Datei im Dateimanager/Windows Explorer/Finder aus, kopieren Sie (Strg+C) und fügen Sie dann in Android Studio ein (Edit/Paste). ##### S. 571 (LocationRequest) Der Konstruktor `LocationRequest()` gilt mittlerweile als *deprecated*. Ein neues `LocationRequest`-Objekt erzeugen Sie nun mit `LocationRequest.create()`. ##### S. 606 (App-Permissions) Für den Internetzugang ist nur `android.permission.INTERNET` erforderlich, nicht aber `android.permission.ACCESS_NETWORK_STATE`. ##### S. 700 (HTMLBuilder / buildString) In `buildString` sollte statt `appendln` (deprecated) besser `appendLine` verwendet werden. ##### S. 758 (App-Permissions) Für den Internetzugang ist nur `android.permission.INTERNET` erforderlich, nicht aber `android.permission.ACCESS_NETWORK_STATE`. ##### S. 764 (Test, ob Programm in Emulator läuft) Auf S. 764 habe ich einen simplen Test vorgestellt, ob eine App im Emulator oder auf einem realen Gerät läuft: ```kotlin private fun isEmulator() = Build.FINGERPRINT.startsWith("generic")

Mittlerweile hat sich die Zeichenkette zur Identifizierung des Geräts geändert. Der Test kann wie folgt umgebaut werden:

fun isEmulator() =
Build.FINGERPRINT.contains("generic_x86")

Einen noch zuverlässigeren, aber deutlich komplizierteren Tests beschreibt StackOverflow.


Letzte Änderung 14.2.2022. Vielen Dank an alle Leser, die mir Feedback geben (und ganz speziell an Herrn Abts, Herrn Krumbiegel, Herrn Sauerbrei und Herrn Zinn)!