Kotlin von Grund auf neu: Pakete und Grundfunktionen

() 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:



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!
- Android SDKEinführung in Android-ArchitekturkomponentenZinn Megali
- Maschinelles LernenErstellen Sie Chatbots auf Android mit IBM WatsonAshraff Hathibelagal
- Android SDKJava vs. Kotlin: Sollten Sie Kotlin für die Android-Entwicklung verwenden?Jessica Thornsby
- Android SDKKurztipp: Schreiben Sie saubereren Code mit Kotlin SAM-KonvertierungenAshraff Hathibelagal
- Android SDKAndroid O: Überprüfung der Telefonnummer mit SMS-TokenChike Mgbemena