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 & 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>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)]}.") // <-- KORREKTUR
}
// Ausgabe: 255 ist nicht enthalten. Die nächstgrößere Zahl
// lautet 259. <-- 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<List>
und List<MutableList>
durcheinandergebracht. Richtig liest sich der Absatz so:
Vielleicht irritiert Sie, dass der Parameter von printMaze
als List<List>
deklariert ist, obwohl die ursprüngliche maze
-Variable den Typ List<MutableList>
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. 252 (Beispielprojekt bankaccount)
overdraft frame ist eine unglückliche Übersetzung für den Überziehungsrahmen. Üblich ist overdraft facility.
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 Destructing, 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. 299 (Eigene Schnittstellen)
Statt valr someProperty: Boolean
muss es var someProperty: Boolean
heißen.
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&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:
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 20.7.2024. Vielen Dank an alle Leser, die mir Feedback geben (und ganz speziell an Herrn Abts, Herrn Krumbiegel, Herrn Sauerbrei und Herrn Zinn)!