Kotlin-Tutorial: Die ersten Schritte mit der neuen Programmiersprache

Erst seit 2016 mit der Version 1.0 verfügbar, dennoch bereits hoch angesehen: Kotlin, eine Alternative zu Java. Die objektbasierte Programmiersprache stammt von JetBrains, einem tschechischen Unternehmen für Software-Entwicklung. Sie überzeugt viele durch ihren schlanken Charakter und deshalb, weil sie nicht so oft Laufzeitfehler produziert – vor allem nicht die gefürchteten NullPointerExceptions. Besonders bei der Entwicklung von Android-Apps ist Kotlin sehr beliebt. Aber auch als Ausgangspunkt für JavaScript-Anwendungen erfreut sich die junge Programmiersprache enormer Beliebtheit.

Kotlin ist eng an Java angelehnt: Zwar sind die beiden Programmiersprachen nicht miteinander kompatibel, dennoch wird Kotlin in Bytecode transformiert, der wiederum von einer Java Virtual Machine (JVM) gelesen werden kann.

Anleitung für Kotlin – mit Beispielen

Um mit Kotlin zu starten, kann der Compiler von der offiziellen Website heruntergeladen werden. Einfacher geht es mit einer Entwicklungsumgebung (IDE): IntelliJ IDEA (auch von JetBrains), Eclipse (mit entsprechendem Plug-in), NetBeans und Android Studio können beispielsweise mit Kotlin umgehen.

Tipp

Die Hersteller von Kotlin halten online eine Sandbox bereit, in der Sie alle Beispiele ausprobieren können.

Pakete (Packages)

Zu Beginn eines Projekts importieren Sie die Pakete, die Sie zur Realisierung Ihres Vorhabens benötigen. Außerdem definieren Sie das Package, an dem Sie gerade arbeiten. Packages enthalten Klassen und Funktionen.

package test.bar
import foo.bar
import footoo.bar as footoo

Um Probleme bei gleichen Namen zu vermeiden, können Sie Packages in Kotlin mit as umbenennen. Die Namensgebung der Packages muss nicht der Verzeichnisstruktur folgen, in der diese zu finden sind. Der Übersicht wegen empfiehlt sich aber dennoch dieses Vorgehen.

Hinweis

Kotlin lädt die wichtigsten Packages automatisch in jedes Projekt.

Im Gegensatz zu Java können Sie bei Kotlin auch einzelne Funktionen aus anderen Paketen importieren. Hierfür gibt man den korrekten Pfad an:

import foo.bar.myFunction

Anschließend kann die Funktion ganz normal verwendet werden.

Tipp

Codezeilen müssen in Kotlin nicht durch eine Markierung – z. B. ein Semikolon – abgeschlossen werden.

Variablen

Kotlin kennt zwei verschiedene Arten von Variablen: Solche, die nur gelesen werden können und festgelegt sind, werden mit val eingeleitet. Andere, deren Wert man im Laufe des Programms verändern kann, leiten Sie mit var ein.

val name = "Clara Oswald"
var age = 22

Im Gegensatz zum feststehenden Namen kann das Alter angepasst werden, etwa in einer Funktion.

Hinweis

In diesem Beispiel hat Kotlin den Typ des Werts der Variablen selbst festgelegt. Es ist auch möglich, diese Basic Types konkret auszuzeichnen.

Basistypen (Basic Types)

Kotlin arbeitet mit bestimmten Typen für Variablen und Klassen. Bei jedem Typ handelt es sich um ein Objekt, und damit unterscheidet sich Kotlin etwas von Java. Während die ältere Programmiersprache primitive Typen erst in einen Wrapper packen muss, damit sie sich wie Objekte verhalten, ist das bei Kotlin nicht nötig. Hier sind wirklich alle Typen bereits Objekte.

Zahlen (Numbers)

In Kotlin können Sie Zahlen ohne eine bestimmte Markierung einsetzen: Der Compiler versteht, dass es sich um Zahlenwerte halten soll. Kommas werden durch Punkte realisiert. Wer möchte, kann auch hexadezimale Zahlen verwenden. Zur besseren Lesbarkeit lassen sich Tausendertrennzeichen mit Unterstrichen darstellen. Kotlin kennt verschiedene Arten von Zahlen, die alle eine unterschiedliche maximale Größe annehmen können:

  • Long: 64 Bit
  • Int: 32 Bit
  • Short: 16 Bit
  • Byte: 8 Bit
  • Double: 64 Bit
  • Float: 32 Bit

Bei Double und Float handelt es sich um Gleitkommazahlen, die sich in komplexen Berechnungen anders verhalten als die Festkommazahlen. Wie alle Typen können Sie Zahlen konkret in Ihrem Code auszeichnen.

val myNumber: Long = 40

Es ist möglich, dass Sie eine Zahl eines bestimmten Typus zu einem anderen konvertieren.

val myInt = 600
val myLong= myInt.toLong()

Der Befehl toLong konvertiert den Int-Wert in einen Long-Wert. Analog funktioniert der Befehl auch abgewandelt für die anderen Typen von Numbers.

String

Bei einem String handelt es sich um Wörter oder ganze Sätze, also um Zeichenketten. Um diese in Kotlin zu verwenden, setzen Sie das Geschriebene in doppelte Anführungszeichen. Möchten Sie mehrere Zeilen Text einbauen, sind drei doppelte Anführungszeichen je zu Beginn und zum Ende nötig (Raw String).

val myString = "Das ist ein einzeiliger String."
val myLongString = """Dies ist ein String,
der über mehrere Zeilen geht."""

Wie in vielen anderen Programmiersprachen ist in Kotlin die Verwendung von Escape Characters möglich: Durch einen Backslash markieren Sie ein Zeichen, das nicht zum eigentlichen String gehört, sondern als Steuerzeichen behandelt werden soll. Umgekehrt lassen sich durch den Backslash auch Zeichen in den String einfügen, die eigentlich in Kotlin eine andere Bedeutung haben. Folgende Escape Characters sind möglich:

  • \t: Tab
  • \b: Backspace
  • \n: Neue Zeile
  • \r: Carriage Return (Wagenrücklauf)
  • \': Einfaches Anführungszeichen
  • \": Doppeltes Anführungszeichen
  • \\: Backslash
  • \$: Dollarzeichen

Das Dollarzeichen dient Ihnen in Strings dazu, Platzhalter einzusetzen. Diese kann man in einem vorherigen Schritt als Variable festlegen. So wird der Platzhalter dann in der Ausgabe durch einen tatsächlichen Wert ersetzt.

val author = "Sandra"
val myString = "Dieser Text stammt von $author"

Zeichen (Characters)

Für einzelne Zeichen stellt Kotlin neben Strings auch den speziellen Datentyp Character zur Verfügung. Statt diese allerdings in doppelte Anführungszeichen zu setzen, verwendet man einfache Anführungszeichen.

var model = 'A'

Boolean

Der Basic Type Boolean gibt einen Wahrheitswert wieder, der entweder wahr (true) oder falsch (false) sein kann.

Arrays

In Kotlin ist ein Array eine Sammlung von Daten. Ein Array bilden Sie mit arrayOf() oder Array(). Die erste Funktion gestaltet sich simpel:

val myArray1 = arrayOf(0, 1, 2, 3, 4, 5)

So erzeugt man ein Array mit den Ziffern von 1 bis 5. In diesen Sammlungen können aber auch andere Typen wie Strings und Booleans untergebracht werden – sogar gemischt. Möchte man das Array auf einen Typ beschränken, gibt man dies in der Funktion mit an.

val myArray2 = arrayOf<int>(10, 20, 30)</int>
val myArray3 = booleanArrayOf(true, true, false)

Der Kotlin-Constructor Array() gestaltet sich komplexer: Hier müssen Sie auch die Länge und eine Lambda-Funktion angeben.

val myArray4 = Array(6, { i -> i })

Der Constructor erzeugt ein Array mit sechs Stellen und beginnt bei null: 0, 1, 2, 3, 4, 5.

Hinweis

Mehr zu Constructors und Lambdas erfahren Sie weiter unten im Text.

Jeder Eintrag in einem Array wird indiziert und kann über diesen Index aufgerufen werden. Dabei verwendet man eckige Klammern und gibt in diesen die Stelle an, die der Eintrag in der Auflistung innehat.

fun main() {
	val myArray5 = arrayOf("Jan", "Maria", "Samuel")
	println(myArray5[2])
}
Hinweis

Die Funktion wird in diesem Fall "Samuel" ausgeben, denn die Zählung beginnt mit 0.

Operatoren

Wie man es aus vielen anderen Programmiersprachen kennt, arbeitet auch Kotlin mit verschiedenen Operatoren, die Sie in Ihren Quellcode einbauen können. Dazu gehören mathematische Operatoren (+, -, *, /, %), Vergleichsoperatoren (<, >, <=, >=, ==, !=) und logische Operatoren (&&, ||, !). Eng mit den Operatoren verwandt sind sogenannte Keywords: Begriffe, die eine feste Bedeutung in Kotlin haben und nicht uminterpretiert werden können.

Tipp

Eine vollständige Liste aller Keywords und Operatoren finden Sie in der offiziellen Dokumentation von Kotlin.

Ranges

In Kotlin beschreibt eine Range einen Typ, der von einem bestimmten Punkt bis zu einem weiteren reicht. Um eine Range zu erzeugen, wendet man den Operator .. an oder nutzt die Funktionen rangeTo() bzw. downTo(). Die Variante mit zwei Punkten hintereinander kann hochzählen. Mit den beiden Funktionen hingegen definieren Sie in eine Richtung.

val range1 = 1..5
val range2 = 1.rangeTo(5)
val range3 = 5.downTo(1)

In diesen einfachen Versionen erzeugen Sie eine Range mit Einer-Schritten. Um die Schrittgröße anzupassen, verwenden Sie zusätzlich die step-Funktion.

val range4 = 0..10 step(2)

Um einzelne Daten in der Range anzusprechen, verwenden Sie den in-Operator. So können Sie beispielsweise eine Abfrage oder auch eine Schleife erzeugen. Um zu überprüfen, ob ein Wert nicht Teil der Range ist, nutzt man den Operator !in.

val range5 = 0..10
fun main() {
	for (n in range5) {
		println(n)
	}
	if (7 in range5) {
		println("yes")
	}
	if (12 !in range5) {
		println("no")
	}
}
Hinweis

Mehr zu Funktionen (fun), Schleifen (for) und Bedingungen (if) erfahren Sie weiter unten im Text.

Funktionen (Functions)

Kotlin-Functions werden immer mit dem Befehl fun erstellt. Im Anschluss definieren Sie den Namen der Funktion, welche Argumente diese enthält und schließlich, was sie macht.

fun div(a: Int, b: Int): Int {
	return a/b
}
fun main() {
	println(div(100, 2))
}

Wir definieren zuerst die Funktion div (für Division) mit zwei Int-Parametern a und b. Die Funktion soll uns das Ergebnis aus der Division von a durch b geben, ebenfalls in Form einer Int-Variablen. In der main-Funktion schließlich rufen wir die zuvor definierte Funktion auf, übergeben ihr konkrete Werte und lassen das Ergebnis durch println (print line) in der Konsole anzeigen. Kotlin führt den Inhalt von main() automatisch aus. Diese Funktion stellt den Einstiegspunkt in ein Kotlin-Programm dar.

Fakt

Außerhalb von Funktionen akzeptiert Kotlin keine Befehle. Nur Deklarationen sind dort erlaubt.

In Kotlin lassen sich Functions, die nur eine Zeile Code umfassen, vereinfacht darstellen. Statt geschweifte Klammern zu öffnen, eine neue, eingeschobene Zeile zu schreiben und die Klammer wieder zu schließen, setzt man ein Gleichheitszeichen ein. Dabei wird zusätzlich auf den Befehl return verzichtet.

fun div(a: Int, b: Int): Int = a/b
fun main() = println(div(100, 2))

Um einen Fehler durch fehlende Parameter zu vermeiden, können Sie beim Definieren der Funktion Standardwerte angeben. Lässt man beim Aufrufen der Funktion die entsprechenden Parameter frei, kommen die Default-Werte zum Einsatz.

fun div(a: Int = 10, b: Int = 5): Int = a/b
fun main() = println(div())

Lambdas

Bei einer Lambda-Funktion (oder anonymen Funktion) handelt es sich um eine Funktion, die weder zu einer Klasse noch zu einem Objekt gehört. Lambdas werden direkt in anderen Funktionen oder Variablen untergebracht. Man ruft sie auf, ohne das Keyword fun zu verwenden. Lambdas lassen sich im Prinzip wie Variablen des Typs val einsetzen und werden auch so erzeugt.

fun main() {
	val myMessage = { println("Hello world!") }
	myMessage()
}

Lambda-Expressions in Kotlin müssen immer in geschweifte Klammern gesetzt werden. Lambdas können auch Funktionsargumente verarbeiten. Diese sind durch einen Pfeil gekennzeichnet, der die Parameter vom Kern des Ausdrucks trennt.

fun main() {
    val div = {a: Int, b: Int -> a/b}
    println(div(6,2))
}

Klassen (Classes)

Genau wie in Java sind Klassen in Kotlin Sammlungen von Daten und Funktionen. Um eine Klasse zu definieren, setzt man einfach das Stichwort class ein. Anschließend kann man die neue Klasse mit Informationen füllen.

class Tardis {
	var year: Int
	var place: String
	constructor(year: Int, place: String) {
		this.year = year
		this.place = place
	}
}

In Kotlin ist der Constructor eine Funktion, die man für die Erstellung von Objekten benötigt. Dafür kennt die Programmiersprache Primary und Secondary Constructors. Primary Constructors sind eine praktische Kurzschreibweise, während Secondary Constructors der Schreibweise in vielen anderen objektorientierten Sprachen ähneln, darunter Java. Diese zweite Variante sehen Sie in obigem Beispiel.

Es gibt aber auch die Möglichkeit, den Secondary Constructor auszulassen und stattdessen einen Primary Constructor einsetzen. Diesen schreiben Sie direkt in die Kopfzeile der Klasse und geben dabei auch die Parameter der Klasse mit an. Dies reduziert die Anzahl der Codezeilen deutlich.

class Tardis constructor(var year: Int, var place: String)

Wenn Sie keine zusätzlichen Angaben in Bezug auf die Sichtbarkeit der Klasse machen möchten (public, private, protected), können Sie auch ganz auf die Nennung des Keywords verzichten.

class Tardis (var year: Int, var place: String)

Alle drei Codebeispiele erzeugen das gleiche Ergebnis.

Diese Klasse können Sie nun in Ihrem weiteren Quelltext einsetzen und mit konkreten Werten füttern.

val tardis1 = Tardis(2133, "Dunlop Station")
val tardis2 = Tardis(1885, "Northhampton")

Wie in den meisten objektorientierten Sprachen üblich, können Sie auf die Eigenschaften und Methoden eines Objekts direkt zugreifen, indem Sie einen Punkt und den Namen der Eigenschaft oder Methode hinter den des Objekts setzen.

class Tardis (var year: Int, var place: String)
val tardis1 = Tardis(2133, "Dunlop Station")
val tardis2 = Tardis(1885, "Northhampton")
fun main() {
    println(tardis1.year)
}

Eine Besonderheit in Kotlin stellt die Data Class dar. Diese Klassenart ist dazu gedacht, nur Daten zu speichern. Prinzipiell reicht hierfür eine Codezeile aus.

data class User (var username: String, var name: String, var age: Int)

Diese Klasse kann direkt eingesetzt werden.

data class User (var username: String, var name: String, var age: Int)
fun main() {
    val user1 = User ("River Song", "Melody Pond", 200)
    println("Username: " + user1.username)
    println("Name: " + user1.name)
    println("Age: " + user1.age)
}

Objekte (Objects)

Objekte sind in Kotlin Instanzen, die so nur ein einziges Mal definiert sein können (Singleton). Sie enthalten in der Regel Variablen und Funktionen. Man kreiert ein Objekt – ähnlich wie eine Klasse – prinzipiell mit nur einer Zeile Code. Dann ist das Objekt allerdings leer. Inhalt geben Sie dem Objekt in dessen Körper.

object myObject {
	fun sum(a: Int, b: Int): Int {
		return a+b
	}
}

Schleifen (Loops)

In Kotlin stehen Ihnen drei verschiedene Schleifenarten zur Verfügung: while, do..while und for. Diese verhalten sich wie ihre Äquivalente in anderen Programmiersprachen. Eine while-Schleife läuft so lang, bis eine festgelegte Bedingung eintritt.

fun main() {
    var n = 1
    while (n <= 10) {
        println(n++)
    }
}

Die do..while-Schleife verhält sich ganz ähnlich zur Variante mit while. Der Unterschied liegt darin, dass der Inhalt der Schleife mindestens einmal durchlaufen wird, da die Überprüfung erst am Ende stattfindet.

fun main() {
    var n = 1
    do {
        n++
    }	
    while (n < 1)
    println(n)
}

Die for-Schleife läuft so lang, wie eine Bedingung wahr bleibt.

val myRange = 0..10
fun main() {
	for (n in myRange) {
		print("$n ")
	}
}

Bedingungen (Conditions)

Kotlin kennt drei verschiedene Möglichkeiten, um bedingte Anweisungen bzw. Verzweigungen umzusetzen: if, if..else und when. Die Abfrage if lässt den Computer eine Aufgabe erledigen, falls die Bedingung zutrifft.

val whoCompanion = arrayOf("Bill Potts", "Clara Oswald", "Amy Pond", "Martha Jones", "Donna Noble", "Rose Tyler")
fun main() {
    if ("Rose Tyler" in whoCompanion) {
        print("yes")
    }
}

Mit else fügen Sie eine Aktion hinzu, die ausgeführt werden soll, wenn die Bedingung nicht zutrifft.

val whoCompanions9 = arrayOf("Rose Tyler")
val whoCompanions10 = arrayOf("Martha Jones", "Donna Noble", "Rose Tyler")
val whoCompanions11 = arrayOf("Clara Oswald", "Amy Pond")
val whoCompanions12 = arrayOf("Bill Potts", "Clara Oswald")
fun main() {
    var whoCompanion = "Clara Oswald"
    if (whoCompanion in whoCompanions9) {
        print("yes")
    }
    else {
        print("no")
    }
}

Der Ausdruck when schließlich ist eine Besonderheit von Kotlin: Abhängig von verschiedenen Zuständen werden unterschiedliche Aktionen durchgeführt. Damit ähnelt der when-Ausdruck dem, was in anderen Programmiersprachen switch löst; er arbeitet aber präziser.

Im Körper von when bringen Sie die unterschiedlichen Überprüfungsfälle unter. Diese stehen immer in Zusammenhang mit einer definierten Variablen.

var age = 17
fun main() {
   when {
    	age > 18 -> println("Du bist zu alt!")
    	age == 18 -> println("Schon erwachsen!")
    	age == 17 -> println("Herzlich willkommen!")
    	age <= 16 -> println("Du bist zu jung!")
   }
}

Das Argument kann allerdings auch direkt an when übergeben werden und muss dann nicht jedes Mal erneut im Körper genannt werden. Darüber hinaus kann eine einzelne Bedingung auch mehrere Aktionen auslösen. Dafür erstellen Sie mit geschweiften Klammern einen neuen Body. Um unerwartete Fälle auszuschließen, hilft Ihnen else.

fun multi(a: Int, b: Int, c: Int): Int {
    return a*b*c
}
fun main() {
    val d = "yes"
    when (d) {
        "no" -> println("Keine Berechnung")
        "yes" -> {
            println("Starte Berechnung") 
            println(multi(5, 2, 100))
            println("Berechnung beendet")
        }
    else -> println("Falsche Eingabe")    
    }
}

Nullability

Ein großer Unmutsfaktor bei der Programmierung mit Java ist der Fehler NullPointerException. Dieser tritt auf, wenn man auf ein Objekt verweist, dessen Wert null ist. Kotlin umgeht dieses Problem, indem es gar nicht erst zulässt, dass Variablen den Wert null annehmen. Sollte der Fall eintreten, erscheint schon beim Kompilieren der Hinweis: „Null can not be a value of a non-null type String“ – oder eine entsprechend andere Warnung.

Nun gibt es aber Momente, in denen möchte man den null-Wert bewusst einsetzen. Dafür verwendet Kotlin den Safe Call Operator: ?.

fun main() {
	var password: String? = null
	print(password)
}

Hiermit erlauben Sie Kotlin explizit, dass null akzeptabel ist. Das Programm wird anschließend null ausgeben. Möchten Sie allerdings eine bestimmte Eigenschaft der Variable ansprechen, müssen Sie den Safe Call Operator erneut einsetzen.

fun main() {
	var password: String? = null
	print(password?.length)
}

Auch dieser Code wird wieder null produzieren, aber eben keinen Error – das Programm läuft. Etwas eleganter ist es, wenn Sie einen Alternativwert angeben. Dafür verwenden Sie den sogenannten Elvis-Operator: ?: – so benannt, weil man die Zeichenfolge als Smiley mit Haartolle lesen kann.

fun main() {
    val firstName = null
    val lastName = "Pond"
	val name: String = firstName?: "Vorname fehlt" + " " + lastName?: "Nachname fehlt"
	print(name)
}

In diesem Beispiel werden Hinweise angezeigt, wenn eine Variable den Wert null annimmt.