Warum Kotlin? Acht Funktionen, die Java-Entwickler zum Wechsel überzeugen könnten

Kotlin wurde 2016 offiziell veröffentlicht und hat in den letzten Jahren viel Aufmerksamkeit auf sich gezogen, insbesondere seit Google die Unterstützung von Kotlin als Alternative zu Java auf Android-Plattformen angekündigt hat. Mit der kürzlich angekündigten Entscheidung, Kotlin zur bevorzugten Sprache für Android zu machen, fragen Sie sich möglicherweise, ob es Zeit ist, eine neue Programmiersprache zu lernen. In diesem Fall kann Ihnen dieser Artikel bei der Entscheidung helfen.

Kotlins Veröffentlichungsgeschichte

Kotlin wurde 2011 angekündigt, aber die erste stabile Version, Version 1.0, erschien erst 2016. Die Sprache ist kostenlos und Open Source, entwickelt von JetBrains mit Andrey Breslav als führendem Sprachdesigner. Kotlin 1.3.40 wurde im Juni 2019 veröffentlicht.

Über Kotlin

Kotlin ist eine moderne, statisch typisierte Programmiersprache, die sowohl objektorientierte als auch funktionale Programmierkonstrukte enthält. Es zielt auf mehrere Plattformen ab, einschließlich der JVM, und ist vollständig mit Java kompatibel. In vielerlei Hinsicht könnte Kotlin so aussehen, wenn Java heute entworfen würde. In diesem Artikel stelle ich acht Funktionen von Kotlin vor, die Java-Entwickler meiner Meinung nach begeistern werden.

  1. Saubere, kompakte Syntax
  2. Einzeltypsystem (fast)
  3. Null Sicherheit
  4. Funktionen und funktionale Programmierung
  5. Datenklassen
  6. Erweiterungen
  7. Überlastung des Bedieners
  8. Objekte der obersten Ebene und das Singleton-Muster

Hallo Welt! Kotlin gegen Java

Listing 1 zeigt das obligatorische "Hallo Welt!" Funktion in Kotlin geschrieben.

Listing 1. "Hallo Welt!" in Kotlin

 fun main() { println("Hello, world!") } 

So einfach es auch ist, dieses Beispiel zeigt die wichtigsten Unterschiede zu Java.

  1. mainist eine Funktion der obersten Ebene; Das heißt, Kotlin-Funktionen müssen nicht in einer Klasse verschachtelt sein.
  2. Es gibt keine public staticModifikatoren. Während Kotlin Sichtbarkeitsmodifikatoren hat, ist publicund kann die Standardeinstellung weggelassen werden. Kotlin unterstützt den staticModifikator ebenfalls nicht , wird jedoch in diesem Fall nicht benötigt, da maines sich um eine Funktion der obersten Ebene handelt.
  3. Seit Kotlin 1.3 ist der Parameter Array-of-Strings für mainnicht erforderlich und kann weggelassen werden, wenn er nicht verwendet wird. Bei Bedarf wird es als deklariert args : Array.
  4. Für die Funktion ist kein Rückgabetyp angegeben. Wo Java verwendet void, verwendet Kotlin Unitund wenn der Rückgabetyp einer Funktion ist Unit, kann er weggelassen werden.
  5. Diese Funktion enthält keine Semikolons. In Kotlin sind Semikolons optional, und daher sind Zeilenumbrüche von Bedeutung.

Das ist eine Übersicht, aber es gibt noch viel mehr darüber zu lernen, wie sich Kotlin von Java unterscheidet und in vielen Fällen verbessert.

1. Sauberere, kompaktere Syntax

Java wird oft als zu ausführlich kritisiert, aber eine gewisse Ausführlichkeit kann Ihr Freund sein, insbesondere wenn es den Quellcode verständlicher macht. Die Herausforderung beim Sprachdesign besteht darin, die Ausführlichkeit zu reduzieren und gleichzeitig die Klarheit zu bewahren. Ich denke, Kotlin trägt wesentlich dazu bei, diese Herausforderung zu meistern.

Wie Sie in Listing 1 gesehen haben, benötigt Kotlin keine Semikolons und ermöglicht das Weglassen des Rückgabetyps für UnitFunktionen. Betrachten wir einige andere Funktionen, die Kotlin zu einer saubereren und kompakteren Alternative zu Java machen.

Typinferenz

In Kotlin können Sie eine Variable als deklarieren var x : Int = 5oder die kürzere, aber ebenso klare Version verwenden var x = 5. (Während Java jetzt varDeklarationen unterstützt , wurde diese Funktion erst in Java 10 angezeigt, lange nachdem die Funktion in Kotlin angezeigt wurde.)

Kotlin hat auch valDeklarationen für schreibgeschützte Variablen, die analog zu Java-Variablen sind, die als deklariert wurden final, was bedeutet, dass die Variable nicht neu zugewiesen werden kann. Listing 2 gibt ein Beispiel.

Listing 2. Schreibgeschützte Variablen in Kotlin

 val x = 5 ... x = 6 // ERROR: WILL NOT COMPILE 

Eigenschaften versus Felder

Wo Java Felder hat, hat Kotlin Eigenschaften. Eigenschaften werden auf ähnliche Weise wie öffentliche Felder in Java deklariert und aufgerufen, Kotlin bietet jedoch Standardimplementierungen von Accessor / Mutator-Funktionen für Eigenschaften. Das heißt, Kotlin bietet get()Funktionen für valEigenschaften und beides get()sowie set()Funktionen für varEigenschaften. Kundenspezifische Versionen von get()und set()können bei Bedarf implementiert werden.

Die meisten Eigenschaften in Kotlin haben Hintergrundfelder, es ist jedoch möglich, eine berechnete Eigenschaft zu definieren , die im Wesentlichen eine get()Funktion ohne Hintergrundfeld ist. Beispielsweise kann eine Klasse, die eine Person darstellt, eine Eigenschaft für dateOfBirthund eine berechnete Eigenschaft für haben age.

Standard versus explizite Importe

Java importiert implizit im Paket definierte Klassen java.lang, aber alle anderen Klassen müssen explizit importiert werden. Infolgedessen importieren viele Java-Quelldateien zunächst Auflistungsklassen von java.util, E / A-Klassen von java.iousw. Standardmäßig Kotlin implizit Importe kotlin.*, die auf Java Import in etwa analog ist java.lang.*, aber Kotlin auch Importe kotlin.io.*, kotlin.collections.*und Klassen aus mehreren anderen Paketen. Aus diesem Grund erfordern Kotlin-Quelldateien normalerweise weniger explizite Importe als Java-Quelldateien, insbesondere für Klassen, die Sammlungen und / oder Standard-E / A verwenden.

Kein Aufruf von 'new' für Konstruktoren

In Kotlin wird das Schlüsselwort newnicht benötigt, um ein neues Objekt zu erstellen. Um einen Konstruktor aufzurufen, verwenden Sie einfach den Klassennamen in Klammern. Der Java-Code

 Student s = new Student(...); // or var s = new Student(...); 

könnte in Kotlin wie folgt geschrieben werden:

 var s = Student(...) 

String-Vorlagen

Zeichenfolgen können Vorlagenausdrücke enthalten. Hierbei handelt es sich um Ausdrücke , die mit in die Zeichenfolge eingefügten Ergebnissen ausgewertet werden. Ein Vorlagenausdruck beginnt mit einem Dollarzeichen ($) und besteht entweder aus einem einfachen Namen oder einem beliebigen Ausdruck in geschweiften Klammern. Zeichenfolgenvorlagen können Zeichenfolgenausdrücke verkürzen, indem die Notwendigkeit einer expliziten Zeichenfolgenverkettung verringert wird. Als Beispiel der folgende Java-Code

 println("Name: " + name + ", Department: " + dept); 

könnte durch den kürzeren, aber äquivalenten Kotlin-Code ersetzt werden.

 println("Name: $name, Department: $dept") 

Erweitert und implementiert

Java-Programmierer wissen, dass eine Klasse eine extendandere Klasse und implementeine oder mehrere Schnittstellen kann. In Kotlin gibt es keinen syntaktischen Unterschied zwischen diesen beiden ähnlichen Konzepten; Kotlin verwendet für beide einen Doppelpunkt. Zum Beispiel der Java-Code

 public class Student extends Person implements Comparable 

would be written more simply in Kotlin as follows:

 class Student : Person, Comparable 

No checked exceptions

Kotlin supports exceptions in a manner similar to Java with one big difference–Kotlin does not have checked exceptions. While they were well intentioned, Java's checked exceptions have been widely criticized. You can still throw and catch exceptions, but the Kotlin compiler does not force you to catch any of them.

Destructuring

Think of destructuring as a simple way of breaking up an object into its constituent parts. A destructuring declaration creates multiple variables at once. Listing 3 below provides a couple of examples. For the first example, assume that variable student is an instance of class Student, which is defined in Listing 12 below. The second example is taken directly from the Kotlin documentation.

Listing 3. Destructuring examples

 val (_, lName, fName) = student // extract first and last name from student object // underscore means we don't need student.id for ((key, value) in map) { // do something with the key and the value } 

'if' statements and expressions

In Kotlin, if can be used for control flow as with Java, but it can also be used as an expression. Java's cryptic ternary operator (?:) is replaced by the clearer but somewhat longer if expression. For example, the Java code

 double max = x >= y ? x : y 

would be written in Kotlin as follows:

val max = if (x >= y) then x else y 

Kotlin is slightly more verbose than Java in this instance, but the syntax is arguably more readable.

'when' replaces 'switch'

My least favorite control structure in C-like languages is the switch statement. Kotlin replaces the switch statement with a when statement. Listing 4 is taken straight from the Kotlin documentation. Notice that break statements are not required, and you can easily include ranges of values.

Listing 4. A 'when' statement in Kotlin

 when (x) { in 1..10 -> print("x is in the range") in validNumbers -> print("x is valid") !in 10..20 -> print("x is outside the range") else -> print("none of the above") } 

Try rewriting Listing 4 as a traditional C/Java switch statement, and you will get an idea of how much better off we are with Kotlin's when statement. Also, similar to if, when can be used as an expression. In that case, the value of the satisfied branch becomes the value of the overall expression.

Switch expressions in Java

Java 12 introduced switch expressions. Similar to Kotlin's when, Java's switch expressions do not require break statements, and they can be used as statements or expressions. See "Loop, switch, or take a break? Deciding and iterating with statements" for more about switch expressions in Java.

2. Single type system (almost)

Java has two separate type systems, primitive types and reference types (a.k.a., objects). There are many reasons why Java includes two separate type systems. Actually that's not true. As outlined in my article A case for keeping primitives in Java, there is really only one reason for primitive types--performance. Similar to Scala, Kotlin has only one type system, in that there is essentially no distinction between primitive types and reference types in Kotlin. Kotlin uses primitive types when possible but will use objects if necessary.

So why the caveat of "almost"? Because Kotlin also has specialized classes to represent arrays of primitive types without the autoboxing overhead: IntArray, DoubleArray, and so forth. On the JVM, DoubleArray is implemented as double[]. Does using DoubleArray really make a difference? Let's see.

Benchmark 1: Matrix multiplication

In making the case for Java primitives, I showed several benchmark results comparing Java primitives, Java wrapper classes, and similar code in other languages. One of the benchmarks was simple matrix multiplication. To compare Kotlin performance to Java, I created two matrix multiplication implementations for Kotlin, one using Array and one using Array . Listing 5 shows the Kotlin implementation using Array.

Listing 5. Matrix multiplication in Kotlin

 fun multiply(a : Array, b : Array) : Array { if (!checkArgs(a, b)) throw Exception("Matrices are not compatible for multiplication") val nRows = a.size val nCols = b[0].size val result = Array(nRows, {_ -> DoubleArray(nCols, {_ -> 0.0})}) for (rowNum in 0 until nRows) { for (colNum in 0 until nCols) { var sum = 0.0 for (i in 0 until a[0].size) sum += a[rowNum][i]*b[i][colNum] result[rowNum][colNum] = sum } } return result } 

Als nächstes verglich ich die Leistung der beiden Kotlin-Versionen mit der von Java mit doubleund Java mit Doubleund führte alle vier Benchmarks auf meinem aktuellen Laptop aus. Da es beim Ausführen jedes Benchmarks ein geringes "Rauschen" gibt, habe ich alle Versionen dreimal ausgeführt und die Ergebnisse gemittelt, die in Tabelle 1 zusammengefasst sind.

Tabelle 1. Laufzeitleistung des Matrixmultiplikations-Benchmarks

Zeitgesteuerte Ergebnisse (in Sekunden)
Java

( double)

Java

( Double)

Kotlin

( DoubleArray)

Kotlin

( Array)

7.30 29,83 6.81 15.82

I was somewhat surprised by these results, and I draw two takeaways. First, Kotlin performance using DoubleArray is clearly superior to Kotlin performance using Array, which is clearly superior to that of Java using the wrapper class Double. And second, Kotlin performance using DoubleArray is comparable to--and in this example slightly better than--Java performance using the primitive type double.

Clearly Kotlin has done a great job of optimizing away the need for separate type systems--with the exception of the need to use classes like DoubleArray instead of Array.

Benchmark 2: SciMark 2.0

My article on primitives also included a second, more scientific benchmark known as SciMark 2.0, which is a Java benchmark for scientific and numerical computing available from the National Institute of Standards and Technology (NIST). The SciMark benchmark measures performance of several computational routines and reports a composite score in approximate Mflops (millions of floating point operations per second). Thus, larger numbers are better for this benchmark.

Mit Hilfe von IntelliJ IDEA habe ich die Java-Version des SciMark-Benchmarks auf Kotlin umgestellt. IntelliJ IDEA konvertierte automatisch double[]und int[]in Java nach DoubleArrayund IntArrayin Kotlin. Ich habe dann die Java-Version mit Primitiven mit der Kotlin-Version mit DoubleArrayund verglichen IntArray. Nach wie vor habe ich beide Versionen dreimal ausgeführt und die Ergebnisse gemittelt, die in Tabelle 2 zusammengefasst sind. Die Tabelle zeigt wieder ungefähr vergleichbare Ergebnisse.

Tabelle 2. Laufzeitleistung des SciMark-Benchmarks

Leistung (in Mflops)
Java Kotlin
1818.22 1815,78