Kotlin-Fallstricke und wie man sie vermeidet

Kotlin ist in letzter Zeit der letzte Schrei. Und obwohl ich der Meinung bin, dass die Sprache gut durchdacht ist, hat sie - wie alles andere auch - ihre Mängel.

In diesem Artikel erkläre ich einige der aufgetretenen Fallstricke und versuche, Ihnen dabei zu helfen, sie zu umgehen.

Das Geheimnis null

In Kotlin können Sie Ihren Code so schreiben, als ob es NULL nie gegeben hätte. Dadurch können Sie vergessen, dass NULL allgegenwärtig ist, sich aber versteckt. Schauen wir uns diese einfache und scheinbar unschuldige Klasse an:

Wenn Sie versuchen, dies zu instanziieren, erhalten Sie eine NullPointerException, da bar vor der Initialisierung versucht hat, auf die Länge von c zuzugreifen.

Natürlich war die Anwendungslogik hier fehlerhaft, aber Sie haben immer noch eine Ausnahme. Das Schlimmste daran ist, dass sich Ihre IDE nicht darüber beschwert.

Das Wichtigste dabei ist, dass Kotlin Ihnen in vielen Fällen (fast allen) hilft, null zu vermeiden, aber Sie können es nicht vergessen, und von Zeit zu Zeit werden Sie auf solche Dinge stoßen.

Behandlung von Nullen aus dem JDK

Kotlins Standardbibliothek verarbeitet Nullen in Ordnung. Wenn Sie jedoch Klassen aus dem JDK verwenden, müssen Sie mögliche Nullzeiger aus Bibliotheksfunktionen manuell verarbeiten.

Meistens reichen die Kotlin-Klassen aus, aber manchmal muss man so etwas wie die ConcurrentHashMap verwenden:

In diesem Fall müssen Sie das benutzen !! Operator. In anderen Fällen kann der Null-Sicherheitsoperator (?) Auch funktionieren. Wenn Sie Java-Bibliotheken häufig verwenden, müssen Sie Ihren Code dennoch mit !! s und? S verunreinigen oder Adapter für Java-Klassen schreiben. Das können Sie nicht wirklich vermeiden.

Es gibt noch ein schrecklicheres Problem, auf das Sie stoßen könnten. Wenn Sie Methoden für JDK-Klassen verwenden, können diese null zurückgeben und enthalten keinen syntaktischen Zucker wie im obigen Map-Zugriff.

Betrachten Sie das folgende Beispiel:

In diesem Fall verwenden Sie peek, was tatsächlich null zurückgeben kann. Der Kotlin-Compiler wird sich jedoch nicht beschweren, sodass Sie eine NullPointerException erhalten können, wenn Ihre Warteschlange leer war.

Das Problem hierbei ist, dass wir Queue verwendet haben, eine JDK-Schnittstelle, und wenn Sie sich die Implementierung von peek ansehen:

Es heißt, dass peek E zurückgibt, was Kotlin glauben lässt, dass E nicht nullbar ist. Dies könnte in einer zukünftigen Version von Kotlin behoben werden, aber im Moment ist es wichtig, dies in Ihren Projekten zu berücksichtigen und Schnittstellen wie diese zu verwenden:

Das innere ist es

Wenn ein Lambda einen einzelnen Parameter hat, können Sie ihn aus Ihrem Code auslassen und stattdessen verwenden:

"It: impliziter Name eines einzelnen Parameters Eine andere hilfreiche Konvention ist, dass, wenn ein Funktionsliteral nur einen Parameter hat, seine Deklaration (zusammen mit ->) weggelassen werden kann und sein Name es sein wird." - Kotlin docs

Das Problem dabei ist, wenn Sie verschachtelte Funktionen wie in diesem Beispiel haben:

Es dauert nur ein paar seiner, um den Überblick zu verlieren, was was ist. Die Lösung für dieses Problem besteht darin, die Parameter explizit zu benennen:

Viel besser!

Die heimtückische Kopie

Schauen Sie sich diese Datenklasse an:

Datenklassen bieten Ihnen eine Reihe von Funktionen und Sie können auch eine Kopie davon erstellen. Ratet mal, was dies ausgeben wird:

Dies wird foobar, wombar, oops drucken. Das Problem ist, dass der Name zwar anzeigt, dass eine Kopie tatsächlich erstellt wird, jedoch nur die Referenzen in Ihrem Objekt kopiert werden. Dies kann heimtückisch sein, wenn Sie vergessen, einen Komponententest zu schreiben, und Ihre Datenklassen so weitergeben, als wären sie unveränderliche Wertobjekte.

Die Lösung für dieses Problem besteht darin, auf Ihre Datenklassen zu achten und wenn es sich um Wertobjekte handelt, machen Sie sie zu einem:

Es gibt ein anderes Problem mit Datenklassen: Sie können Kotlin nicht mitteilen, welche Felder Sie in Ihren equals / hashCode einfügen möchten, Sie können nur beide überschreiben und sie von Hand schreiben. Behalte dies im Kopf.

Interne Leckage

Schauen Sie sich dieses Beispiel an:

Wenn Sie Klassen wie diese aus anderen Kotlin-Projekten verwenden, wird das interne Schlüsselwort berücksichtigt. Wenn Sie dies von einem Java-Projekt aus betrachten, ist hiddenOperation jedoch öffentlich! Um dies zu vermeiden, würde ich vorschlagen, Schnittstellen zu verwenden, um Implementierungsdetails auszublenden:

Nicht generische globale Erweiterungen

Der Nutzen von Erweiterungsfunktionen ist zweifellos hoch, aber mit großer Leistung geht große Verantwortung einher. Sie können beispielsweise Erweiterungsfunktionen in JDK-Klassen schreiben, die für das gesamte Projekt sichtbar sind. Dies kann problematisch sein, wenn sie nicht generisch sind und Vorgänge darstellen, die nur in einem lokalen Kontext sinnvoll sind:

Jetzt kratzen sich alle an Ihrem Projekt die Köpfe, wenn sie darauf stoßen. Ich denke, es ist gut, wenn Sie zweimal überlegen, bevor Sie Erweiterungsfunktionen schreiben, aber sie können sehr leistungsfähig sein. Hier sind einige Beispiele, die nützlich sein könnten:

Einheit, die Lambdas gegen Java-SAM-Konvertierung zurückgibt

Wenn Sie Funktionen haben, die Lambdas akzeptieren, können Sie das return-Schlüsselwort weglassen, wenn der return-Typ des Lambdas Unit ist:

Dies ist in Ordnung, aber wenn Sie dies von Java aus aufrufen, werden Sie mit dem unangenehmen Problem konfrontiert, dass Sie Unit zurückgeben müssen:

Dies ist von Java-Seite sehr klobig. Wenn Sie versuchen, diese Funktion von Java aus auszuführen, können Sie eine Schnittstelle definieren:

Dann können Sie die SAM-Konvertierung von Java verwenden, um dies sehr einfach zu gestalten:

aber dann von der Kotlin-Seite wird es ein Chaos:

Das Problem ist, dass Kotlin keine SAM-Konvertierung für Kotlin-Klassen unterstützt und nur mit Java-Klassen funktioniert. Ich schlage vor, dass Sie für einfache Fälle nur Java-basierte SAM-Schnittstellen wie Consumer verwenden:

Java-Interop mit nicht veränderbaren Collections

Kotlin bietet unveränderliche Varianten der JDK-Sammlungsklassen:

Dies ist eine sehr schöne Ergänzung, aber wenn Sie dies von der Java-Seite aus betrachten, sehen Sie die JDK-Set-API:

Wenn Sie versuchen, dieses Set zu ändern, geschieht dasselbe, als hätten Sie Javas Collections.unmodifiableSet () -Methode verwendet. Ich weiß nicht, ob dies umgangen werden kann (oder sollte), aber das ist etwas, woran Sie denken können, wenn Sie mit Kotlins unveränderlichen Versionen von Java-Sammlungen arbeiten.

Keine Überlastung der Schnittstellen

Dies ist nur aus Interop-Sicht ein Problem, aber Kotlin unterstützt die Annotation @JvmOverloads in einer Schnittstelle nicht und Sie können sie auch nicht für Überschreibungen verwenden:

Derzeit können Sie Überladungen nur manuell definieren:

Beachten Sie jedoch, dass Sie mithilfe von KEEP (Kotlin Evolution and Enhancement Process) Verbesserungen für Kotlin selbst vorschlagen können. KEEP ist so etwas wie JEP in Java, aber KEEP hat natürlich viel weniger Bürokratie als JEP.

Fazit

Kotlin ist derzeit eine sehr beliebte Sprache und ich stimme zu, dass es sich um ein Turbo Java handelt, aber Sie sollten jeden Hype mit ein wenig Salz aufnehmen. Wie wir oben gesehen haben, hat Kotlin seine eigenen Fallstricke, die Sie berücksichtigen sollten, wenn Sie vorhaben, es zu verwenden.

Alles in allem denke ich, dass die oben genannten Probleme entweder einfach oder nicht kritisch gelöst werden können und die Benutzerfreundlichkeit der Sprache selbst nicht einschränken.

Danke fürs Lesen! Sie können mehr von meinen Artikeln auf meinem Blog lesen.