Zu neuen Ufern:
Migration von Java nach Kotlin
Dieser Artikel richtet sich an Programmierer mit Java-Erfahrung, die sich näher mit Kotlin beschäftigen wollen und einen Einstieg in die Thematik suchen.
Mit der Ankündigung von Google am 7. Mai dieses Jahres (2019), Kotlin zur bevorzugten Sprache für die Android-Entwicklung zu machen, stellen sich die Fragen: Wie aufwändig ist der Umstieg, und wie geht man am besten vor?
Durch die Interoperabilität mit Java ist es möglich, übergangsweise beide Formen parallel zu verwenden, bis eine vollständige Migration abgeschlossen ist, also ist ein schrittweises Vorgehen kein Problem. Android Studio bringt bereits einige Hilfen zur Unterstützung von Kotlin mit, unter anderem Tools zur automatischen Übersetzung von Java-Klassen. Das entbindet einen jedoch nicht, sich mit den Unterschieden zwischen Java und Kotlin auseinanderzusetzen.
Sowohl https://kotlinlang.org/docs/ als auch https://developer.android.com/kotlin/ bieten einen guten Einstieg und Überblick, Android Developers auch durch viele Codebeispiele, welche direkt vergleichbar in beiden Sprachen sind. Hier sollen ein paar Elemente daraus vorgestellt werden.
Hauptunterschiede
Bei einer ersten Betrachtung fällt gleich auf, dass Code in Kotlin deutlich kürzer daher kommt. Weniger Schreiben bedeutet weniger Lesen und Fixen – und schont nicht zuletzt auch das Mausrad.
Vergleichen wir einmal eine exemplarische Java-Klasse mit ihrem Kotlin-Gegenpart:
public class JavaClass extends AbstractClass implements Interface { public final static String DEFAULT = ""; private final String string; public Integer integer; public JavaClass() throws EnvironmentException { this(DEFAULT, 0, new Date()); } public JavaClass(String string, int integer, Date date) throws EnvironmentException { this.string = string; this.integer = integer; Environment.init(date); } @Override public String toString() { return String.format("%d %s", integer, string); } }
class KotlinClass constructor(string: String, integer: Int, date: Date) : AbstractClass(), Interface { companion object { public const val DEFAULT = "" } constructor() : this("", 0, Date()) private val string = string public var integer = integer init { Environment.init(date) } override fun toString(): String { return "$integer $string" } }
Mit var
, val
und fun
werden jeweils Objekte und Methoden definiert. var
steht vor veränderlichen (variable), val
vor unveränderlichen (value) und fun
vor Methoden (function). Kotlin unterstützt damit die Verwendung von sogenannten Immutables, deren Status sich im Prozessverlauf nicht verändern kann.
Von den Sichtbarkeiten ist der Standard immer public, protected und private müssen explizit angegeben werden. Den Java-default package-private gibt es bei Kotlin nicht. (Zur Erinnerung: Diese sind sichtbar für alle Klassen in demselben Package. Protected erweitert dieses Verhalten auf alle abgeleiteten Klassen.)
final
kann bei nicht überschreibbaren Methoden ebenfalls angegeben werden, bei Variablen erledigt sich das durch die Verwendung von val
.
static
fehlt in Kotlin ebenfalls. Unter Java werden so Methoden und Variablen definiert, die nicht einer einzelnen Instanz angehören, sondern dem Klassenobjekt selber zugeordnet sind, welches seine eigene Instanz besitzt. Bei Kotlin heißt dieses companion object
und muß eigens definiert werden. Innerhalb diesem kann man seine val
auch als const val
In der Kotlin-Klasse sieht man zudem, dass der primäre Konstruktor mit der Klassendefinition zusammenfällt. Um dessen Parameter in Instanzvariablen zu speichern, kann man entweder diese entsprechend zuweisen oder kürzer, die Parameter selber als var
oder val
definieren.
Weitere, sekundäre Konstruktoren können natürlich innerhalb der Klasse definiert werden, und eventuelle zusätzliche Logik (z.B. Parsen von Parametern) erfolgt in einem eigenen init
– Block, ähnlich einem static { }
in Java.
Der Kotlin-Code kann sogar noch weiter verkürzt werden:
class KotlinClass( private val string: String, var integer: Int, date: Date ) : AbstractClass(), Interface { companion object { const val DEFAULT = "" } constructor() : this("", 0, Date()) init { Environment.init(date) } override fun toString() = "$integer $string" }
Weitere Eigenschaften, die hier zu erkennen sind:
- Das Semikolon
;
am Zeilenende kann weggelassen werden, es ist nur zwischen zwei Ausdrücken in derselben Zeile notwendig. new
bei der Objekt-Instanziierung entfällt.- Es gibt keine Checked Exceptions. Nur noch tatsächlich zu behandelnde Runtime Exceptions müssen gefangen werden.
- Typen werden bei der direkten Zuweisung erkannt.
override
erbt automatisch die Sichtbarkeit (protected
oderpublic
), diese muss nicht explizit angegeben werden.
Viele Default-Code-Elemente können, weil sie redundant sind, weggelassen aber auch trotzdem zur Klarheit beibehalten werden. Kotlin bietet mehrere Möglichkeiten, den Code zu gestalten, von Java-nah bis zu Scala-like, die sich nicht gegenseitig ausschließen.