Blog
Kotlin lernen - Eine schöne Sprache für die JVM

Kotlin ist eine statisch typisierte Programmiersprache, die von Jetbrains, dem Erfinder der IntelliJ IDE-Plattform, entwickelt wurde. Die Sprache wurde 2016 zum ersten Mal veröffentlicht und hat seither viel Zuspruch erhalten. Kotlin wird als Backend-Sprache, als Front-End-Sprache und auf der mobilen Plattform, insbesondere Android, eingesetzt. Kotlin entwickelt sich in einem enormen Tempo weiter und wird auf allen Plattformen verfügbar sein, von JVM über JavaScript bis hin zu nativen Sprachen. Zeit, uns Kotlin beizubringen!
Was ist Kotlin
Kotlin ist eine generische Programmiersprache für mehrere Plattformen und Paradigmen, die mit verschiedenen Compilern geliefert wird. Mit den Kotlin-Compilern kann Code zu JVM-Bytecode kompiliert werden, der auf der Java Virtual Machine ausgeführt werden kann. Es gibt auch einen JavaScript-Compiler, so dass der Code in Browsern oder der NodeJS-Laufzeitumgebung ausgeführt werden kann. Es gibt auch einen LLVM-Compiler, mit dem Kotlin zu einer statischen Binärdatei kompiliert werden kann.
Die Syntax von Kotlin ist stark von Scala inspiriert, um die Sprache sehr prägnant zu machen, aber es fehlen Funktionen, die Kotlin sehr leicht erlernbar machen. Durch den Verzicht auf fortgeschrittene Sprachfunktionen wird die Sprache pragmatischer und für die breite Öffentlichkeit nutzbar. Kotlin wird allgemein als eine freundliche Sprache mit gerade genug Abstraktionen angesehen, die Kotlin für den täglichen Gebrauch geeignet machen.
Die Ausgewogenheit von gerade genug Typinferenz, funktionalen Abstraktionen, Multi-Plattform-Integration und Geschwindigkeit ist der Grund, warum die Sprache so beliebt ist. Die Syntax ist zwar nicht mit Java kompatibel, aber die JVM-Implementierung der Kotlin-Standardbibliothek ist so konzipiert, dass sie mit Java-Code und -Bibliotheken interoperabel ist. Kotlin verwendet die Typinferenz, um den Umfang der Sprache zu reduzieren, und unterstützt eine ganze Reihe funktionaler Sprachfunktionen und fortschrittlicher Primitive für die Gleichzeitigkeit.
Einige Annahmen
Bevor wir fortfahren, gehe ich davon aus, dass Sie einen Mac haben, mit Homebrew eingerichtet sind und Erfahrung mit JVM-Sprachen wie Java oder Scala haben. Die meisten der Beispiele werden mit der REPL ausgeführt.
Kotlin installieren
Auf einem Mac können Sie Kotlin installieren, indem Sie Folgendes eingeben.
$ brew install kotlin
$ kotlin -version
Kotlin version 1.3.10-release-253 (JRE 1.8.0_172-b11)
$ kotlinc -version
info: kotlinc-jvm 1.3.10 (JRE 1.8.0_172-b11)
$ kotlinc-jvm -version
info: kotlinc-jvm 1.3.10 (JRE 1.8.0_172-b11)
$ kotlinc-js -version
info: kotlinc-js 1.3.10 (JRE 1.8.0_172-b11)
REPL
Kotlin verfügt über eine Read Evaluate Print Loop (REPL):
$ kotlinc
Welcome to Kotlin version 1.3.10 (JRE 1.8.0_172-b11)
Type :help for help, :quit for quit
>>> :help
Available commands:
:help show this help
:quit exit the interpreter
:dump bytecode dump classes to terminal
:load <file> load script from specified file
>>> listOf(1, 2, 3).map { it + 2 }.map { it * 2 }.filter { it > 6 }.sum()
18
>>> :quit
Kotlin lernen
Es gibt eine Menge Ressourcen, um Kotlin zu lernen. Kotlin bietet eine
Gradle Build Tool
Kotlin kann mit jedem Tool erstellt werden, da die Compiler als Kommandozeilen-Tools verfügbar sind, aber der bevorzugte Weg zur Erstellung von Kotlin-Programmen ist Gradle. Wenn Sie wissen möchten, wie Sie Gradle verwenden, lesen Sie meinen früheren Blog Learning Gradle - An Open Source Build Automation Tool.
Datentypen
Kotlin unterstützt die folgenden grundlegenden Datentypen:
val a: Double = 2.0
val b: Float = 2.0f
val c: Long = 1L
val d: Int = 1
val e: Short = 1
val f: Byte = 1
val g: Int = 0x0F
val h: Int = 0b00001011
val i: Char = 'a'
val j: Boolean = true
val k: UInt = 1u
val l: UShort = 1u
val m: UInt = 1u
val n: ULong = 1u
// octal is not supported
println("$a $b $c $d $e $f $g $h $i $j $k $l $m $n")
Streicher
Kotlin hat die folgenden String-Darstellungen:
val a: String = "foo"
val b: String = "bar" + "baz"
val c: String = "Hello Worldn"
val d: String = """Multi
Line
String"""
val e: String = """
|Lorem ipsum dolor amet tousled coloring book pickled pug church-key, disrupt PBR&B schlitz celiac.
|Taxidermy pork belly celiac, shabby chic drinking vinegar seitan etsy retro banjo listicle ramps.
|Paleo blog shoreditch, taxidermy gastropub bespoke yuccie hexagon hammock sartorial ethical everyday
|carry typewriter messenger bag cliche.
""".trimMargin()
String-Interpolation:
val a = 1
val b = 2
val c = a + b
val d = "$a + $b = $c"
Funktionen
Kotlin unterstützt Funktionen:
fun double(x: Int): Int {
return x + 1
}
val x = double(1)
Lambdas und anonyme Funktionen:
Ein Lambda-Ausdruck ist immer von geschweiften Klammern umgeben. In Kotlin gibt es die Konvention, dass, wenn der letzte Parameter einer Funktion eine Funktion akzeptiert, ein Lambda-Ausdruck, der als entsprechendes Argument übergeben wird, außerhalb der Klammern platziert werden kann. Wenn eine Funktion ein einziges Argument hat, ist der Funktionsparameter unter dem Namen it wird vom Compiler hergeleitet.
fun g(x: Int, f: (Int) -> Int): (Int) -> Int {
// return a lambda
return { y -> f(x) + y }
}
fun h(x: Int, f: (Int) -> Int): (Int) -> Int {
// return an anonymous function
return fun (y: Int): Int { return f(x) + y }
}
val a = g(2) { x -> x + 5}
val a = g(2) { it + 5} // single argument
val b = a(10)
val c = h(2) { x -> x + 5}
val c = h(2) { it + 5} // single argument
val d = c(10)
println("$b $d")
Paket und Importe
Wie Java verwendet Kotlin Pakete und Importe, um Funktionen zu partitionieren und zu gruppieren und Namenskollisionen zu vermeiden. Eine Kotlin-Quelldatei beginnt mit einer package Deklaration, gefolgt von einer oder mehreren Import-Anweisungen.
package baz.bar
import foo.Bar
import foo.Bar as fBar
import foo.*
fun foo() {}
class Bar
Jede Kotlin-Datei importiert standardmäßig die folgenden Pakete:
- kotlin.*
- kotlin.annotation.*
- kotlin.collections.*
- kotlin.comparisons.*
- kotlin.io.*
- kotlin.ranges.*
- kotlin.sequences.*
- kotlin.text.*
- java.lang.*
- kotlin.jvm.*
Mit dem Schlüsselwort import können Sie Klassen, Top-Level-Funktionen und Eigenschaften sowie Enum-Konstanten importieren.
Klassen
Kotlin unterstützt die Definition von Klassen:
class Person(val name: String, val age: Int) {
fun walk(): Unit {
println("$name is walking")
}
}
val p = Person("Dennis", 42)
p.walk()
Daten-Klassen
Kotlin unterstützt die Definition von Datenklassen, d.h. Klassen, die nur dazu da sind, Daten zu speichern. Das generische Konzept von Datenklassen wird als 'Datensätze' bezeichnet. Der Compiler erstellt automatisch ein 'equals()/hashCode()-Paar', eine 'toString()', 'componentN()' Funktionen, die den Eigenschaften in der Reihenfolge ihrer Deklaration entsprechen und eine 'copy()' Funktion.
data class Person(val name: String, val age: Int)
val dennis = Person("Dennis", 42)
val (name, age) = dennis
println("$name $age $dennis")
Kontrollfluss
Kotlin bietet Kontrollflüsse, die Ausdrücke sind, d.h. sie geben einen Wert zurück.
val x = 2
val y = if (x > 1) x else 1
println(y)
Kotlin bietet When-Ausdrücke. Für Entwickler, die von Scala kommen, ist es kein Mustervergleich. Es gibt keine Dekomposition, d.h. Sie können keine Datenklassen dekomponieren.
val x = 2
val y = when {
x > 1 -> x
else -> 1
}
println(y)
Fehlerbehandlung
Für die Behandlung von Fehlern verwendet Kotlin die Syntax try-catch-finally. Kotin hat keine geprüften Ausnahmen, was großartig ist!
val x: Int = try {
1/0
} catch (e: ArithmeticException) {
println(e)
0
} finally {
0
}
println(x)
Null Sicherheit
Kotlin eliminiert Null-Referenzen mit Hilfe des elvis-Operators.
Kotlin fügt die folgenden Operatoren hinzu:
?.Aufruf: Führt einen sicheren Aufruf aus (ruft eine Methode auf oder greift auf eine Eigenschaft zu, wenn der Empfänger nicht leer ist).?:elvis: nimmt den rechten Wert, wenn der linke Wert Null ist (der elvis-Operator)
val x: String? = null // define a nullable type
val y = if (x != null) x.length else -1
val z = x?.length ?: -1
println("$y $z")
Sammlungen
Kotlin bietet List, Set und Map.
val xs: List<int> = listOf(1, 2, 3).map { it + 2}
println(xs)
xs.forEach { println(it) }
val ys: List<int> = setOf(1, 1, 2, 2, 3, 3).map { it + 2}
println(ys)
ys.forEach { println(it) }
val zs: List<int> = xs.flatMap { x -> ys.map { it + x }}
println(zs)
zs.forEach { println(it) }
val kv: Map<string, String> = hashMapOf("name" to "Dennis", "age" to 42).mapValues { it.toString() }
kv.forEach { k, v -> println("k=$k, v=${v.javaClass}")}
Reichweite
Kotlin bietet Bereiche:
for (i in 1..10 step 2) println(i)
for (i in 10 downTo 1 step 2) println(i)
for (i in (1..10).reversed()) println(i)
for (i in 1.rangeTo(10)) println(i)
for (i in (1..10).map { it + 1 }.filter {it % 3 == 0}) println(i)
Erweiterungsmethoden
Erweiterungsmethoden ermöglichen die Erweiterung bestehender Klassen um neue Funktionen, ohne dass Sie von der Klasse erben oder eine Art von Entwurfsmuster wie Decorator verwenden müssen. Erstellen Sie eine Funktion mit dem Namen des Typs, wie Int, und geben Sie den Namen der neuen Funktion ein. Der Wert des Typs ist über die Referenz T und fügen die neue Funktion hinzu.
fun Int.sayMyName(): String {
return "Dennis"
}
fun Int.sayMyNameAndAge(): String {
return "Dennis $this"
}
fun Int.sayMyNameAndAdd(x: Int): String {
return "Dennis ${this + x}"
}
fun <t : Any> T.whoAmI(): String {
return "Dennis and I am ${this.javaClass}"
}
println(42.sayMyName())
Dennis
println(42.sayMyNameAndAge())
Dennis 42
println(42.sayMyNameAndAdd(10))
Dennis 52
println(listOf(1, 2, 3).whoAmI())
Dennis and I am class java.util.Arrays$ArrayList
Funktionale Programmierung
Funktionale Datenstrukturen, Muster und Typklassen können der Standardbibliothek von Kotlin mit Hilfe der Arrow-Bibliothek hinzugefügt werden. Arrow ist quelloffen und auf github
verfügbar. Fügen Sie Folgendes zu build.gradle.kts hinzu:
dependencies {
// Use the Kotlin JDK 8 standard library
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// add arrow
val arrow_version = "0.8.1"
compile("io.arrow-kt:arrow-core:$arrow_version")
compile("io.arrow-kt:arrow-syntax:$arrow_version")
compile("io.arrow-kt:arrow-typeclasses:$arrow_version")
compile("io.arrow-kt:arrow-data:$arrow_version")
compile("io.arrow-kt:arrow-instances-core:$arrow_version")
compile("io.arrow-kt:arrow-instances-data:$arrow_version")
kapt("io.arrow-kt:arrow-annotations-processor:$arrow_version")
// Use the Kotlin test library
testImplementation("org.jetbrains.kotlin:kotlin-test")
// Use the Kotlin JUnit integration
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}
Verwenden Sie die funktionalen Datenstrukturen:
import arrow.core.Try
import arrow.instances.list.foldable.fold
import arrow.instances.monoid
val xs = listOf(1, 2, 3)
val x = xs.fold(Int.monoid())
println(x)
val y = Try { 1/0 }
val z: Int = y.fold({-1}){it}
println(z)
Gleichzeitigkeit
Coroutines sind eine Kotlin-Funktion, die asynchrone Rückrufe für langlaufende Aufgaben, wie z.B. Datenbank- oder Netzwerkzugriffe, in sequenziellen Code umwandelt. Für eine gute Einführung in Coroutines empfehle ich Ihnen Introduction to Coroutines von Roman Elizarov
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1")
}
Beispiel: Hallo Welt
import kotlinx.coroutines.*
fun main(args: Array<string>) {
GlobalScope.launch {
// launch new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}
Beispiel: Starten von 10.000 Coroutines. Die Anwendung läuft 5 Sekunden lang und alle Coroutines werden beendet.
import kotlinx.coroutines.*
suspend fun main(args: Array<string>) {
val jobs = List(10000) {
GlobalScope.launch {
delay(5000)
print(".")
}
}
jobs.forEach { it.join() }
}
Zufallszahlengenerator
Im Folgenden werden Zufallszahlen generiert:
// generate a random number
for (x in 1..10) println((1..10).random())
// generate the same random number sequence every time
import kotlin.random.Random
val rnd = Random(1)
for (x in 1..10) println(rnd.nextInt(10))
UUID
Die JDK-Standardbibliothek kann eine UUID erzeugen.
import java.util.UUID
val uuid = UUID.randomUUID()
Dateien schreiben
Kotlin bietet Erweiterungsmethoden für java.io.File, um Dateien zu schreiben:
- File.writeText(text: String): Setzt den Inhalt dieser Datei als Text.
- File.writeBytes(array: ByteArray): Legt den Inhalt dieser Datei als Array von Bytes fest. Wenn diese Datei bereits existiert, wird sie überschrieben.
- File.printWriter(): Liefert einen neuen PrintWriter zum Schreiben des Inhalts dieser Datei.
- File.bufferedWriter(): Liefert einen neuen BufferedWriter zum Schreiben des Inhalts dieser Datei.
java.io.File("/tmp/test.json").writeText("""{"message": "Hello World!"}""")
Dateien lesen
Kotlin bietet Erweiterungsmethoden für java.io.File, um Dateien zu lesen:
- File.readText(): Ruft den gesamten Inhalt dieser Datei als String im Format UTF-8 ab.
- File.ReadLines(): Liest den Inhalt der Datei als eine Liste von Zeilen.
- File.forEachLine(): Liest diese Datei Zeile für Zeile.
- File.readBytes(): Ruft den gesamten Inhalt dieser Datei als Byte-Array ab.
- File.bufferedReader(): Liefert einen neuen BufferedReader zum Lesen des Inhalts dieser Datei.
val text = java.io.File("/tmp/test.json").readText()
println(text)
JSON-Kodierung
Moshi ist eine JSON-Bibliothek, die Unterstützung für Kotlin-Datenklassen bietet. Um Moshi einzurichten, fügen Sie hinzu:
dependencies {
implementation("com.squareup.moshi:moshi-kotlin:1.8.0")
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.8.0")
}
Ein Beispiel:
import com.squareup.moshi.JsonClass
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
@JsonClass(generateAdapter = true)
data class Person(val name: String, val age: Int)
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val personAdapter = moshi.adapter(Person::class.java)
val dennis = Person("Dennis", 42)
val json = personAdapter.toJson(dennis)
println(json)
val dennisFromJson = personAdapter.fromJson("""{"name":"Dennis","age":42}""")
println(dennisFromJson)
Fazit
Kotlin ist eine sehr schöne Programmiersprache. Die Syntax und die Funktionen der Sprache erinnern mich an die Programmiersprachen Groovy und Scala. Der beste Vergleich wäre vielleicht, dass es sich um eine Mischung aus diesen beiden Sprachen handelt. Kotlin ist sowohl für die JVM als auch für die Android-Plattform verfügbar und es gibt ein natives Kotlin-Projekt, um Kotlin für Systemprogrammierer verfügbar zu machen.
Eine Funktion, die ich vermisse, ist der Musterabgleich - ein Mechanismus zur Überprüfung eines Wertes anhand eines Musters. Es ist in Kotlin nicht möglich, Datenklassen mit einem Muster abzugleichen. Der Musterabgleich ist eine großartige Funktion, die der Sprache hoffentlich noch hinzugefügt wird.
Kotlin bietet weder Implicits noch implizite Auflösung, eine Funktion, die den Code wirklich aufräumen kann, wenn der Code Daten wie bei der JSON- oder AVRO-Serialisierung verarbeitet.
Die Arrow-Bibliothek bietet funktionale Datenstrukturen und Typklassen für diejenigen, die ohne Semigruppen, Monoide, Funktoren, Monaden und dergleichen nicht leben können.
Was ich an Kotlin wirklich mag, sind Coroutines, eine Kotlin-Funktion, die asynchrone Rückrufe für langlaufende Aufgaben, wie z.B. Datenbank- oder Netzwerkzugriffe, in sequenziellen Code umwandelt.
Für Projekte, die nicht den Aufwand von Scala benötigen, aber die prägnante Syntax und Flexibilität einer modernen Programmiersprache auf der JVM wünschen, würde ich zu Kotlin raten.
Verfasst von
Dennis Vriend
Contact