Advertisement
  1. Code
  2. Coding Fundamentals

Kotlin von Grund auf neu: Pakete und Grundfunktionen

Scroll to top
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Ranges and Collections
Kotlin From Scratch: More Fun With Functions

() translation by (you can also view the original English article)

Kotlin ist eine moderne Programmiersprache, die zu Java-Bytecode kompiliert wird. Es ist kostenlos und Open Source und verspricht, das Codieren für Android noch lustiger zu machen.

Im vorherigen Artikel haben Sie mehr über Sortimente und Sammlungen in Kotlin erfahren. In diesem Tutorial lernen wir die Sprache weiter, indem wir uns ansehen, wie Code mithilfe von Paketen organisiert wird, und fahren dann mit einer Einführung in die Funktionen in Kotlin fort.

1. Pakete

Wenn Sie mit Java vertraut sind, wissen Sie, dass Java Pakete verwendet, um verwandte Klassen zu gruppieren. Das Paket java.util enthält beispielsweise eine Reihe nützlicher Dienstprogrammklassen. Pakete werden mit dem Schlüsselwort package deklariert, und jede Kotlin-Datei mit einer package-Deklaration am Anfang kann Deklarationen von Klassen, Funktionen oder Schnittstellen enthalten.

Erklärung

Mit Blick auf den folgenden Code haben wir ein Paket com.chikekotlin.projectx mit dem Schlüsselwort package deklariert. Außerdem haben wir in diesem Paket eine Klasse MyClass deklariert (wir werden in zukünftigen Beiträgen Klassen in Kotlin diskutieren).

1
package com.chikekotlin.projectx
2
3
class MyClass

Der vollständig qualifizierte Name für die Klasse MyClass lautet jetzt com.chikekotlin.projectx.MyClass.

1
package com.chikekotlin.projectx
2
3
fun saySomething(): String {
4
    return "How far?"
5
}

Im obigen Code haben wir eine Funktion der obersten Ebene erstellt (wir werden in Kürze darauf zurückkommen). Ähnlich wie bei MyClass lautet der vollständig qualifizierte Name für die Funktion saySomething() com.chikekotlin.projectx.saySomething.

Importe

In Kotlin verwenden wir die import-Deklaration, damit der Compiler die zu importierenden Klassen, Funktionen, Schnittstellen oder Objekte finden kann. In Java hingegen können wir keine Funktionen oder Methoden direkt importieren - nur Klassen oder Schnittstellen.

Wir verwenden den import, um auf eine Funktion, Schnittstelle, Klasse oder ein Objekt außerhalb des Pakets zuzugreifen, in dem es deklariert wurde.

1
import com.chikekotlin.projectx.saySomething
2
3
fun main(args: Array<String>) {
4
    saySomething() // will print "How far?"

5
}

Im obigen Code-Snippet haben wir die Funktion saySomething() aus einem anderen Paket importiert und diese Funktion dann ausgeführt.

Kotlin unterstützt auch Platzhalterimporte mit dem Operator *. Dadurch werden alle im Paket deklarierten Klassen, Schnittstellen und Funktionen gleichzeitig importiert. Dies wird jedoch nicht empfohlen - normalerweise ist es besser, Ihre Importe explizit zu machen.

1
import com.chikekotlin.projectx.*

Aliasing importieren

Wenn Sie Bibliotheken mit widersprüchlichen Klassen- oder Funktionsnamen haben (z. B. deklarieren sie jeweils eine Funktion mit demselben Namen), können Sie das Schlüsselwort as verwenden, um dieser importierten Entität einen temporären Namen zu geben.

1
import com.chikekotlin.projectx.saySomething
2
import com.chikekotlin.projecty.saySomething as projectYSaySomething
3
4
fun main(args: Array<String>) {
5
    projectYSaySomething() 
6
}

Beachten Sie, dass der temporäre Name nur in der Datei verwendet wird, in der er zugewiesen wurde.

2. Funktionen

Eine Funktion gruppiert eine Reihe von Code-Anweisungen, die eine Aufgabe ausführen. Die Details der Implementierung der Funktion sind dem Aufrufer verborgen.

In Kotlin werden Funktionen mithilfe des Schlüsselworts fun definiert, wie im folgenden Beispiel gezeigt:

1
fun hello(name: String): String {
2
    return "Hello $name"
3
}
4
val message = hello("Chike")
5
print(message) // will print "Hello Chike"

Im obigen Code haben wir eine einfache Funktion hello() mit einem einzelnen Parameter-name vom Typ String definiert. Diese Funktion gibt einen String-Typ zurück. Das Parameterdefinitionsformat für Funktionen ist name: type, z. B. age: Int, price: Double, student: StudentClass.

1
fun hello(name: String): Unit {
2
   print("Hello $name")
3
}
4
5
hello("Chike") // will print "Hello Chike"

Die obige Funktion ähnelt der vorherigen, beachten Sie jedoch, dass diese einen Rückgabetyp von Unit hat. Da diese Funktion keinen signifikanten Wert an uns zurückgibt - sie druckt nur eine Nachricht aus -, ist ihr Rückgabetyp standardmäßig Unit. Unit ist ein Kotlin-Objekt (wir werden Kotlin-Objekte in späteren Beiträgen diskutieren), das den Void-Typen in Java und C ähnelt.

1
public object Unit {
2
    override fun toString() = "kotlin.Unit"
3
}

Beachten Sie, dass der Typ vom Compiler abgeleitet wird, wenn Sie den Rückgabetyp nicht explizit als Unit deklarieren.

1
fun hello(name: String) { // will still compile

2
   print("Hello $name")
3
}

Einzeilige Funktionen

Einzeilige oder einzeilige Funktionen sind Funktionen, die nur einzelne Ausdrücke sind. In dieser Funktion werden die geschweiften Klammern entfernt und das Symbol = vor dem Ausdruck verwendet. Mit anderen Worten, wir werden den Funktionsblock los.

1
fun calCircumference(radius: Double): Double {
2
    return (2 * Math.PI) * radius
3
}

Die obige Funktion kann in eine einzelne Zeile gekürzt werden:

1
fun calCircumference(radius: Double) = (2 * Math.PI) * radius

Wenn Sie sich die aktualisierte Funktion oben ansehen, können Sie sehen, dass wir unseren Code präziser gestaltet haben, indem wir die geschweiften Klammern {}, das Schlüsselwort return und auch den Rückgabetyp (der vom Compiler abgeleitet wird) entfernt haben.

Sie können den Rückgabetyp weiterhin angeben, um ihn expliziter zu gestalten, wenn Sie möchten.

1
fun calCircumference(radius: Double): Double = (2 * Math.PI) * radius

Benannte Parameter

Benannte Parameter ermöglichen besser lesbare Funktionen, indem die Parameter benannt werden, die beim Aufruf an eine Funktion übergeben werden.

Im folgenden Beispiel haben wir eine Funktion erstellt, die meinen vollständigen Namen druckt.

1
fun sayMyFullName(firstName: String, lastName: String, middleName: String): Unit {
2
    print("My full name is $firstName $middleName $lastName");
3
}

Um die obige Funktion auszuführen, würden wir sie einfach so nennen:

1
sayMyFullName("Chike", "Nnamdi", "Mgbemena")

Wenn wir uns den obigen Funktionsaufruf ansehen, wissen wir nicht, welche Argumente vom Typ String welchen Funktionsparametern entsprechen (obwohl einige IDEs wie IntelliJ IDEA uns helfen können). Benutzer der Funktion müssen die Funktionssignatur (oder den Quellcode) oder die Dokumentation überprüfen, um zu wissen, was jeder Parameter entspricht.

1
sayMyFullName(firstName = "Chike", middleName = "Nnamdi", lastName = "Mgbemena")

Im zweiten Funktionsaufruf oben haben wir die Parameternamen vor den Argumentwerten angegeben. Sie sehen, dass dieser Funktionsaufruf klarer und lesbarer ist als der vorherige. Diese Art des Aufrufs von Funktionen trägt dazu bei, die Möglichkeit von Fehlern zu verringern, die auftreten können, wenn Argumente desselben Typs versehentlich ausgetauscht werden.

Der Aufrufer kann auch die Reihenfolge der Parameter mithilfe benannter Parameter ändern. Beispielsweise:

1
sayMyFullName(lastName = "Mgbemena", middleName = "Nnamdi", firstName = "Chike") // will still compile

Im obigen Code haben wir die Argumentposition des firstName gegen den lastName ausgetauscht. Die Argumentreihenfolge spielt bei benannten Parametern keine Rolle, da der Compiler jeden von ihnen dem richtigen Funktionsparameter zuordnet.

Standardparameter

In Kotlin können wir einer Funktion Standardwerte für jeden ihrer Parameter geben. Diese Standardwerte werden verwendet, wenn den Argumenten während des Funktionsaufrufs nichts zugewiesen wird. Um dies in Java zu tun, müssten wir verschiedene überladene Methoden erstellen.

Hier haben wir in unserer calCircumference()-Methode die Methode geändert, indem wir einen Standardwert für den pi-Parameter hinzugefügt haben - Math.PI, eine Konstante aus dem Paket java.lang.Math.

1
fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius

Wenn wir diese Funktion aufrufen, können wir entweder unseren ungefähren Wert für pi übergeben oder den Standardwert verwenden.

1
print(calCircumference(24.0)) // used default value for PI and prints 150.79644737231007

2
print(calCircumference(24.0, 3.14)) // passed value for PI and prints 150.72

Sehen wir uns ein anderes Beispiel an.

1
fun printName(firstName: String, middleName: String = "N/A", lastName: String) {
2
    println("first name: $firstName - middle name: $middleName - last name: $lastName")
3
}

Im folgenden Code haben wir versucht, die Funktion aufzurufen, sie wird jedoch nicht kompiliert:

1
printName("Chike", "Mgbemena") // won't compile

Im obigen Funktionsaufruf übergebe ich meinen Vor- und Nachnamen an die Funktion und hoffe, den Standardwert für den zweiten Vornamen verwenden zu können. Dies wird jedoch nicht kompiliert, da der Compiler verwirrt ist. Es weiß nicht, wofür das Argument "Mgbemena" ist - ist es für den Parameter MiddleName oder LastName?

Um dieses Problem zu lösen, können wir benannte Parameter und Standardparameter kombinieren.

1
printName("Chike", lastName = "Mgbemena") // will now compile

Java-Interoperabilität

Da Java in Methoden keine Standardparameterwerte unterstützt, müssen Sie alle Parameterwerte explizit angeben, wenn Sie eine Kotlin-Funktion von Java aus aufrufen. Kotlin bietet uns jedoch die Funktionalität, um es den Java-Aufrufern zu erleichtern, indem die Kotlin-Funktion mit @JvmOverloads kommentiert wird. Diese Anmerkung weist den Kotlin-Compiler an, die überladenen Java-Funktionen für uns zu generieren.

Im folgenden Beispiel haben wir die Funktion calCirumference() mit @JvmOverloads kommentiert.

1
@JvmOverloads
2
fun calCircumference(radius: Double, pi: Double = Math.PI): Double = (2 * pi) * radius

Der folgende Code wurde vom Kotlin-Compiler generiert, damit Java-Aufrufer auswählen können, welchen sie aufrufen möchten.

1
// Java

2
double calCircumference(double radius, double pi);
3
double calCircumference(double radius);

In der zuletzt generierten Java-Methodendefinition wurde der pi-Parameter weggelassen. Dies bedeutet, dass die Methode den Standard-pi-Wert verwendet.

Unbegrenzte Argumente

In Java können wir eine Methode erstellen, um eine nicht angegebene Anzahl von Argumenten zu erhalten, indem wir nach einem Typ in die Parameterliste der Methode ein Auslassungszeichen (...) einfügen. Dieses Konzept wird auch von Kotlin-Funktionen unterstützt, bei denen der Modifikator vararg gefolgt vom Parameternamen verwendet wird.

1
fun printInts(vararg ints: Int): Unit {
2
    for (n in ints) {
3
        print("$n\t")
4
    }
5
}
6
printInts(1, 2, 3, 4, 5, 6) // will print 1    2	3	4	5	6

Mit dem Modifikator vararg können Aufrufer eine durch Kommas getrennte Liste von Argumenten übergeben. Hinter den Kulissen wird diese Liste von Argumenten in ein Array eingeschlossen.

Wenn eine Funktion mehrere Parameter hat, ist der vararg-Parameter normalerweise der letzte. Es ist auch möglich, Parameter nach dem vararg zu haben, aber Sie müssen benannte Parameter verwenden, um sie anzugeben, wenn Sie die Funktion aufrufen.

1
fun printNumbers(myDouble: Double, myFloat: Float, vararg ints: Int) {
2
    println(myDouble)
3
    println(myFloat)
4
    for (n in ints) {
5
        print("$n\t")
6
    }
7
}
8
printNumbers(1.34, 4.4F, 2, 3, 4, 5, 6) // will compile

Im obigen Code befindet sich beispielsweise der Parameter mit dem Modifikator vararg an der letzten Position in einer Liste mit mehreren Parametern (dies ist normalerweise der Fall). Aber was ist, wenn wir es nicht in der letzten Position wollen? Im folgenden Beispiel befindet es sich an zweiter Stelle.

1
fun printNumbers(myDouble: Double, vararg ints: Int, myFloat: Float) {
2
    println(myDouble)
3
    println(myFloat)
4
    for (n in ints) {
5
        print("$n\t")
6
    }
7
}
8
printNumbers(1.34, 2, 3, 4, 5, 6, myFloat = 4.4F) // will compile

9
printNumbers(1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will not compile

10
printNumbers(myDouble = 1.34, ints = 2, 3, 4, 5, 6, myFloat = 4.4F) // will also not

11
compile

Wie Sie im aktualisierten Code oben sehen können, haben wir beim letzten Parameter benannte Argumente verwendet, um dies zu lösen.

Spread Operator

Angenommen, wir möchten ein Array von Ganzzahlen an unsere Funktion printNumbers() übergeben. Die Funktion erwartet jedoch, dass die Werte in einer Liste von Parametern abgewickelt werden. Wenn Sie versuchen, das Array direkt an printNumbers() zu übergeben, wird es nicht kompiliert.

1
val intsArray: IntArray = intArrayOf(1, 3, 4, 5)
2
printNumbers(1.34, intsArray, myFloat = 4.4F) // won't compile

Um dieses Problem zu lösen, müssen wir den Spread-Operator * verwenden. Dieser Operator entpackt das Array und übergibt die einzelnen Elemente als Argumente an die Funktion für uns.

1
val intsArray: IntArray = intArrayOf(1, 3, 4, 5)
2
printNumbers(1.34, *intsArray, myFloat = 4.4F) // will now compile

Durch Einfügen des Spread-Operators * vor intsArray in die Argumentliste der Funktion wird der Code jetzt kompiliert und das gleiche Ergebnis erzielt, als hätten wir die Elemente von intsArray als durch Kommas getrennte Liste von Argumenten übergeben.

Mehrere Werte zurückgeben

Manchmal möchten wir mehrere Werte von einer Funktion zurückgeben. Eine Möglichkeit besteht darin, den Pair-Typ in Kotlin zu verwenden, um ein Pair zu erstellen und es dann zurückzugeben. Diese Pair-Struktur enthält zwei Werte, auf die später zugegriffen werden kann. Dieser Kotlin-Typ kann alle Typen akzeptieren, die Sie als Konstruktor angeben. Außerdem müssen die beiden Typen nicht einmal gleich sein.

1
fun getUserNameAndState(id: Int): Pair<String?, String?> {
2
    require(id > 0, { "Error: id is less than 0" })
3
4
    val userNames: Map<Int, String> = mapOf(101 to "Chike", 102 to "Segun", 104 to "Jane")
5
    val userStates: Map<Int, String> = mapOf(101 to "Lagos", 102 to "Imo", 104 to "Enugu")
6
7
    val userName = userNames[id]
8
    val userState = userStates[id]
9
    return Pair(userName, userState)
10
}

In der obigen Funktion haben wir ein neues Pair erstellt, indem wir die Variablen userName und userState als erstes bzw. zweites Argument an den Konstruktor übergeben und dieses Pair dann an den Aufrufer zurückgegeben haben.

Zu beachten ist auch, dass wir in der Funktion getUserNameAndState() eine Funktion namens require() verwendet haben. Diese Hilfsfunktion aus der Standardbibliothek wird verwendet, um unseren Funktionsaufrufern eine Vorbedingung zu geben, die erfüllt werden muss. Andernfalls wird eine IllegalArgumentException ausgelöst (Ausnahmen in Kotlin werden in einem zukünftigen Beitrag behandelt). Das optionale zweite Argument für require() ist ein Funktionsliteral, das eine Nachricht zurückgibt, die angezeigt wird, wenn die Ausnahme ausgelöst wird. Wenn Sie beispielsweise die Funktion getUserNameAndState() aufrufen und -1 als Argument übergeben, wird Folgendes ausgelöst:

IntelliJ IDEA code execution resultIntelliJ IDEA code execution resultIntelliJ IDEA code execution result

Abrufen von Daten aus dem Pair

1
val userNameAndStatePair: Pair<String?, String?> = getUserNameAndState(101)
2
println(userNameAndStatePair.first) // Chike

3
println(userNameAndStatePair.second) // Lagos

Im obigen Code haben wir mithilfe der ersten und zweiten Eigenschaften auf den first und second Wert des Pair-Typs zugegriffen.

Es gibt jedoch einen besseren Weg, dies zu tun: die Destrukturierung.

1
val (name, state) = getUserNameAndState(101)
2
println(name) // Chike

3
println(state) // Lagos

Was wir im obigen aktualisierten Code getan haben, ist, den ersten und zweiten Wert des zurückgegebenen Pair-Typs direkt dem Variablen-name und -state zuzuweisen. Diese Funktion wird als Destrukturierungsdeklaration bezeichnet.

Dreifache Rückgabewerte und darüber hinaus

Was ist nun, wenn Sie drei Werte gleichzeitig zurückgeben möchten? Kotlin bietet uns einen weiteren nützlichen Typ namens Triple.

1
fun getUserNameStateAndAge(id: Int): Triple<String?, String?, Int> {
2
    require(id > 0, { "id is less than 0" })
3
    
4
    val userNames: Map<Int, String> = mapOf(101 to "Chike", 102 to "Segun", 104 to "Jane")
5
    val userStates: Map<Int, String> = mapOf(101 to "Lagos", 102 to "Imo", 104 to "Enugu")
6
    
7
    val userName = userNames[id]
8
    val userState = userStates[id]
9
    val userAge = 6
10
    return Triple(userNames[id], userStates[id], userAge)
11
}
12
13
val (name, state, age) = getUserNameStateAndAge(101)
14
println(name) // Chike

15
println(state) // Lagos

16
println(age) // 6

Ich bin sicher, einige von Ihnen fragen sich, was Sie tun sollen, wenn Sie mehr als drei Werte zurückgeben möchten. Die Antwort darauf wird in einem späteren Beitrag sein, wenn wir Kotlins Datenklassen diskutieren.

Abschluss

In diesem Tutorial haben Sie die Pakete und Grundfunktionen der Programmiersprache Kotlin kennengelernt. Im nächsten Tutorial der Kotlin From Scratch-Reihe erfahren Sie mehr über Funktionen in Kotlin. Bis bald!

Um mehr über die Kotlin-Sprache zu erfahren, empfehle ich, die Kotlin-Dokumentation zu besuchen. Oder sehen Sie sich einige unserer anderen Beiträge zur Entwicklung von Android-Apps hier auf Envato Tuts+ an!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.