Kotlin statt Java: Effizienter entwickeln

Kotlin hat als Java-Herausforderer einen steilen Aufstieg genommen. Dank klarer Struktur und strikter Standardvorgaben lassen sich typische Fehler vermeiden.

In Pocket speichern vorlesen Druckansicht 160 Kommentare lesen

(Bild: Shutterstock)

Lesezeit: 19 Min.
Von
  • Benjamin Schmid
  • Ralph Guderlei
Inhaltsverzeichnis

Eine ausgereifte Programmiersprache, die prägnant, sicher, eingängig und interoperabel mit Java und anderen JVM-Sprachen ist. Die Entwurfsziele von Kotlin sind schnell benannt, und das Wichtigste ist: Kotlin soll Spaß machen und produktives Programmieren fördern.

Bereits 2011 stellte JetBrains seine Programmiersprache Kotlin offiziell vor, gönnte sich danach aber knapp fünf weitere Jahre für Experimente und Reifeprozess. Das Unternehmen gab die Java-Alternative 2016 für den produktiven Einsatz frei und garantierte Langzeit-Support. Die deutliche Ausrichtung auf einen stabilen und ausgereiften Sprachkern mit modernen Sprachfeatures und zugleich voller Java-Interoperabilität überzeugte: 2019 ernannte Google Kotlin zur präferierten Sprache für die Android-Entwicklung und gründete gemeinsam mit JetBrains die gemeinnützige Kotlin Foundation. Diese lenkt seitdem alle Belange der Sprache und treibt ihre Entwicklung voran.

Auf den Erfolg von Kotlin hat sich über die Jahre auch das Java-Ökosystem eingestellt: Etablierte Framework-Größen wie Spring, Vaadin, Vert.x, Quarkus oder Micronaut bekennen sich explizit zu Kotlin. Auch für das konservative Umfeld ist es also an der Zeit, einen Blick über den Tellerrand zu wagen und sich mit dieser Java-Sprachalternative für den industriellen Einsatz näher zu beschäftigen.

Kotlin inklusive seines Ökosystems eignet sich als General Purpose Language für ein breites Aufgabenspektrum. Neben den klassischen Feldern der Backend-Entwicklung und dem Programmieren nativer Android-Apps lassen sich mit Kotlin mobile Cross-Plattform-Apps, JavaScript-Anwendungen beispielsweise mit React und sogar nativer Machinencode mit LLVM erstellen. Das Wiederverwenden von Code zwischen den Plattformen ist über das Auslagern in Multiplattform-Bibliotheken ebenfalls möglich. Der folgende Text fokussiert sich auf die grundlegenden Eigenschaften der Sprache und die Entwicklung für die JVM.

Der besondere Charme von Kotlin liegt in der Ausdrucksstärke und guten Lesbarkeit der Sprache. Einen ersten Eindruck vermittelt folgendes erweitertes "Hello World". Das Beispiel sollte für Java-Entwicklerinnen und -Entwickler gut verständlich sein:

import java.time.Instant

fun main(args: Array<String>) {
    val name = if (args.size > 0) args[0] else "Leser"
    val leser = Gast(name, anrede = Anrede.werter)

    println("Hallo ${leser.anrede} $name!")
    println(leser)
}

enum class Anrede { Herr, Frau, werter }

data class Gast(val name: String,
                var zeit: Instant = Instant.now(),
                val anrede: Anrede?)

Das Beispiel zeigt bereits einige der Eigenschaften, die das Arbeiten mit Kotlin effizient gestalten. Dazu gehören die prägnanten Schlüsselwörter wie fun für Funktionen, das Arbeiten ohne Klassen durch Top Level Functions, die optionalen Semikolons am Zeilenende oder in String Templates eingebundene Ausdrücke. Funktionsparameter können Default-Werte tragen und werden damit optional. Die Angabe von new zum Erzeugen neuer Objektinstanzen entfällt.

Prägnanz und Kürze gehören zu Kotlins Stärken. Als statisch und streng typisierte Sprache bietet Kotlin Typsicherheit und erspart dennoch Tipparbeit durch mehr Intelligenz seitens des Compilers. Über Typinferenz erschließt er den Typ der lokalen Variablen und Funktionssignaturen, sodass explizite Typangaben üblicherweise nur an Schnittstellen notwendig sind. Mit Type-safe Builders und etwas syntaktischem Zucker lassen sich darüber sogar eigene Domain-specific Languages erstellen wie die Kotlin-DSL von Gradle. Durch den stärkeren funktionalen Fokus hat in Kotlin das Thema Unveränderbarkeit (Immutability) mehr Gewicht als bei Java: Es ist einfacher und kürzer, Klassen, Variablen, Attribute und Collections in einer rein lesbaren beziehungsweise unveränderbaren Form zu deklarieren.

Ein zentraler Sicherheitsbaustein ist die dedizierte Behandlung von null-Werten im Typsystem. Das zwingt Entwicklerinnen und Entwickler, potenzielle null-Werte korrekt zu behandeln und vermeidet Java-typische Laufzeitfehler weitgehend. Dass die Vorgaben in der Praxis kaum nennenswerten Mehraufwand nach sich ziehen, ist zahlreichen Helferlein und nicht zuletzt einem pragmatischen Umgang mit Rückgabewerten aus Java-Code zu verdanken.

Sonderheft "Programmiersprachen – Next Generation"

Dieser Artikel stammt aus dem neuen iX-Developer-Sonderheft "Programmiersprachen – Next Generation". Es beschäftigt sich auf 156 Seiten schwerpunktmäßig mit den Sprachen TypeScript, Kotlin, Rust und Go. Daneben wirft es einen Blick auf aktuelle Entwicklungen bei Java, C++ und der Programmierung von Quantencomputern.

Die PDF-Ausgabe des Sonderhefts ist zum Preis von 12,99 Euro im heise-shop verfügbar. Die gedruckte Ausgabe kostet 14,90 Euro. Außerdem bietet der heise Shop ein Bundle aus gedruckter Ausgabe plus PDF für 19,90 Euro an. Das Heft ist zudem in gut sortierten Kiosken und Buchhandlungen verfügbar.

Trotz der Mehrwerte ist und bleibt die volle Java-Interoperabilität eines der obersten Entwurfsziele von Kotlin. Jede Java-Klasse und jedes Java-Framework lässt sich direkt verwenden. Anwendungen können Kotlin und Java frei mischen. Java-Konstrukte wie Felder oder Java-Record-Datentypen, die in Kotlin nicht direkt vorgesehen sind, generiert der Compiler auf Anfrage beispielsweise über Annotationen. Die nahtlose Interoperabilität ist nicht zuletzt dem Umstand zu verdanken, dass Kotlin auf die bestehenden JDK-Klassen setzt und beispielsweise keine konkurrierenden Runtime-Implementierungen für Collections oder andere Datentypen mitbringt. Stattdessen bildet der Compiler die Kotlin-Varianten transparent mit den Java-Typen ab.

Da Kotlin trotz seiner Knappheit eine intuitive Lesbarkeit aufweist, ist in der Praxis die Lernkurve angenehm flach. Routinierten Java-Entwicklern und -Entwicklerinnen genügt oft etwas Basiswissen und der Wunsch nach eleganterem Code, um sich – geführt durch Compiler und die Autovervollständigung der IDE – den Großteil der Kotlin-Funktionsweise zu erschließen. Dort, wo Intuition nicht mehr genügt, hilft ein kurzer Blick in die gelungene Sprachreferenz.

Beim ersten Kennenlernen und zum schnellen Ausprobieren ist der Kotlin Playground praktisch. Wer nach dem Lesen dieses Artikels Lust auf mehr bekommt und ganz praktisch einzelne Aspekte vertiefen möchte, mag die Kotlin Koans hilfreich finden: Das interaktive, strukturierte Curriculum aus 43 Lerneinheiten bietet Interessierten zahlreiche kleine Aufgaben an, die sich im Browser oder komfortabler über das Plug-in EduTools in IntelliJ IDEA oder Android Studio verwenden lassen (s. Abb. 1).

Das Plug-in EduTools führt durch die Kotlin Koans und gibt Hilfestellungen zum Finden der Lösung (Abb. 1).

Wer Java entwickelt, bewegt sich bei Kotlin weiter im gewohnten Umfeld. Die ebenfalls von JetBrains stammende Entwicklungsumgebung IntelliJ IDEA und ihr Spin-off Android Studio bringen die erforderlichen Werkzeuge mit. In Eclipse oder Visual Studio Code rüsten Plug-ins Unterstützung nach, können aber hinsichtlich des Komforts und der Qualität nicht mithalten. Im Gegensatz zu anderen JVM-Sprachen bringt Kotlin kein eigenes Build-Tool mit. Stattdessen kann man die aus Java gewohnten Werkzeuge wie Ant, Gradle oder Maven über Compiler-Plug-ins nutzen.

Der Kotlin-Compiler ist angenehm schnell und verarbeitet sowohl Kotlin- als auch Java-Code. Ein automatisierter Java-zu-Kotlin-Konverter der IDE hilft bei der inkrementellen Migration (s. Abb. 2) und bietet zahlreiche Vorschläge für die weitere Optimierung. Somit gestaltet sich für Bestandsprojekte ein stufenweiser Umstieg von Java nach Kotlin risikoarm. Die hinzugewonnene Sicherheit beispielsweise bezüglich null-Safety hat dabei schon manchen Bug zutage gebracht.

Automatische Konverter helfen bei der Migration von Java nach Kotlin (Abb. 2).

Das Einstiegsbeispiel kam teilweise noch ohne sie aus, aber auch in Kotlin spielen Klassen eine zentrale Rolle und lassen sich daher in einfachen Fällen schnell und komfortabel deklarieren: Der primäre Konstruktor direkt am Klassennamen erlaubt es, eine Klasse samt zugehöriger Properties in einem Rutsch zu definieren.

class Heft(val num: Int, 
           var titel: String)

Der folgende Code erzeugt eine komplette Klasse mit zwei Attributen. Dabei definiert var eine änderbare und val eine rein lesbare Property. Das Schlüsselwort constructor dient zum Definieren zusätzlicher oder komplexerer Konstruktoren mit Annotationen oder reinen Parametern, die allerdings den primary constructor – sofern vorhanden – verwenden müssen.

class Rechteck {
  var breite: Int
  var laenge: Int
  val flaeche  // read-only Property als Getter
    get() = breite * laenge

  constructor (b: Int, l: Int) {
    breite = b
    laenge = l
  }

  constructor(seite: Int) : this(seite, seite)
}

Vergleichbar mit .NET ersetzen Properties die typische Java-Beans-Kombination aus privatem Feld plus Getter- und Setter-Methode. Die Zugriffsmethoden generiert der Kotlin-Compiler automatisch transparent. In anderer Richtung präsentieren sich Getter/Setter-Kombinationen aus Java- oder JDK-Klassen als Properties in Kotlin (s. Abb. 3).

Getter/Setter-Kombinationen aus Java-Klassen versteht Kotlin als Properties (Abb. 3).

Die Datenklasse Gast aus dem anfangs gezeigten "Hello World"-Beispiel ist ein einfacher, erweiterbarer Wertecontainer, der viel üblichen Boilerplate-Code erspart. Dank des zusätzlichen Schlüsselworts data werden automatisch die Funktionen equals(), hashCode(), toString(), copy() und componentN() basierend auf den Properties und ihrer Reihenfolge in der Klasse erzeugt.

Somit entspricht eine Data Class den üblichen Konventionen für Java Beans bei einem Bruchteil der Tipparbeit. Das Java-11-Äquivalent bringt es noch auf stattliche 100 Zeilen. In Java 14 hat mit den Records inzwischen ein ähnliches Konzept Einzug gehalten, allerdings nur für rein lesbare Wertecontainer und ohne die Mehrwertfunktionen. Über die Annotation @JvmRecord erzeugt der Kotlin-Compiler aus einer Datenklasse alternativ nur einen Java 14 Record.

Folgende Tabelle zeigt Java-Konstrukte mit ihren Pendants in Kotlin:

Java Kotlin
Datentypen
Object Any
void Unit
int Int
java.lang.Integer Int?
Integer[] Array<Int>
int[] IntArray
List bzw. List<?> List<out Any?>!
Konstrukte
final var a = new X(5); val a = X(5)
public final class X {...} class X {...}