Advertisement
  1. Code
  2. Kotlin

Kotlin von Grund auf neu: Mehr Spaß mit Funktionen

by
Read Time:15 minsLanguages:
This post is part of a series called Kotlin From Scratch.
Kotlin From Scratch: Packages and Basic Functions
Kotlin From Scratch: Advanced Functions

German (Deutsch) translation by Władysław Łucyszyn (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 Programmieren für Android noch mehr Spaß zu machen.

Im vorherigen Artikel haben Sie etwas über Pakete und grundlegende Funktionen in Kotlin erfahren. Funktionen sind das Herzstück von Kotlin, daher werden wir uns in diesem Beitrag genauer damit befassen. Wir werden die folgenden Arten von Funktionen in Kotlin untersuchen:

  • Funktionen auf höchstem Niveau
  • Lambda-Ausdrücke oder Funktionsliterale
  • anonyme Funktionen
  • lokale oder verschachtelte Funktionen
  • Infix-Funktionen
  • Mitgliedsfunktionen

Sie werden erstaunt sein, was für coole Dinge Sie mit Funktionen in Kotlin machen können!

1. Funktionen der obersten Ebene

Funktionen der obersten Ebene sind Funktionen innerhalb eines Kotlin-Pakets, die außerhalb einer Klasse, eines Objekts oder einer Schnittstelle definiert sind. Dies bedeutet, dass es sich um Funktionen handelt, die Sie direkt aufrufen, ohne dass Sie ein Objekt erstellen oder eine Klasse aufrufen müssen.

Wenn Sie ein Java-Programmierer sind, wissen Sie, dass wir normalerweise statische Dienstprogrammmethoden innerhalb von Hilfsklassen erstellen. Diese Hilfsklassen tun nicht wirklich etwas – sie haben keine Zustands- oder Instanzmethoden und fungieren nur als Container für die statischen Methoden. Ein typisches Beispiel ist die Collections-Klasse im Paket java.util und ihre statischen Methoden.

Funktionen der obersten Ebene in Kotlin können als Ersatz für die statischen Dienstprogrammmethoden innerhalb von Hilfsklassen verwendet werden, die wir in Java codieren. Sehen wir uns an, wie Sie eine Top-Level-Funktion in Kotlin definieren.

Im obigen Code haben wir ein Paket com.chikekotlin.projectx.utils in einer Datei namens UserUtils.kt definiert und auch eine Dienstprogrammfunktion der obersten Ebene namens checkUserStatus() innerhalb desselben Pakets und derselben Datei definiert. Der Kürze halber gibt diese sehr einfache Funktion den String "online" zurück.

Als Nächstes verwenden wir diese Dienstprogrammfunktion in einem anderen Paket oder einer anderen Datei.

Im vorherigen Code haben wir die Funktion in ein anderes Paket importiert und dann ausgeführt! Wie Sie sehen, müssen wir kein Objekt erstellen oder auf eine Klasse verweisen, um diese Funktion aufzurufen.

Java-Interoperabilität

Da Java keine Top-Level-Funktionen unterstützt, erstellt der Kotlin-Compiler im Hintergrund eine Java-Klasse, und die einzelnen Top-Level-Funktionen werden in statische Methoden umgewandelt. In unserem eigenen Fall war die generierte Java-Klasse UserUtilsKt mit einer statischen Methode checkUserStatus().

Dies bedeutet, dass Java-Aufrufer die Methode einfach aufrufen können, indem sie auf ihre generierte Klasse verweisen, genau wie bei jeder anderen statischen Methode.

Beachten Sie, dass wir den Java-Klassennamen, den der Kotlin-Compiler generiert, mithilfe der Annotation @JvmName ändern können.

Im obigen Code haben wir die Annotation @JvmName angewendet und einen Klassennamen UserUtils für die generierte Datei angegeben. Beachten Sie auch, dass diese Anmerkung am Anfang der Kotlin-Datei vor der Paketdefinition steht.

Es kann von Java wie folgt referenziert werden:

2. Lambda-Ausdrücke

Lambda-Ausdrücke (oder Funktionsliterale) sind auch nicht an eine Entität wie eine Klasse, ein Objekt oder eine Schnittstelle gebunden. Sie können als Argumente an andere Funktionen übergeben werden, die als Funktionen höherer Ordnung bezeichnet werden (wir werden diese im nächsten Beitrag näher besprechen). Ein Lambda-Ausdruck stellt nur den Block einer Funktion dar, und seine Verwendung reduziert das Rauschen in unserem Code.

Wenn Sie ein Java-Programmierer sind, wissen Sie, dass Java 8 und höher Lambda-Ausdrücke unterstützen. Um Lambda-Ausdrücke in einem Projekt zu verwenden, das frühere Java-Versionen wie Java 7, 6 oder 5 unterstützt, können wir die beliebte Retrolambda-Bibliothek verwenden.

Eines der großartigen Dinge an Kotlin ist, dass Lambda-Ausdrücke sofort unterstützt werden. Da Lambda in Java 6 oder 7 nicht unterstützt wird, damit Kotlin damit interoperieren kann, erstellt Kotlin im Hintergrund eine anonyme Java-Klasse. Beachten Sie jedoch, dass das Erstellen eines Lambda-Ausdrucks in Kotlin ganz anders ist als in Java.

Hier sind die Merkmale eines Lambda-Ausdrucks in Kotlin:

  • Es muss von geschweiften Klammern {} umgeben sein.
  • Es hat nicht das Schlüsselwort fun.
  • Es gibt keinen Zugriffsmodifikator (private, public oder protected), da er keiner Klasse, keinem Objekt oder keiner Schnittstelle angehört.
  • Es hat keinen Funktionsnamen. Mit anderen Worten, es ist anonym.
  • Es wird kein Rückgabetyp angegeben, da er vom Compiler abgeleitet wird.
  • Parameter werden nicht von Klammern () umgeben.

Außerdem können wir einer Variablen einen Lambda-Ausdruck zuweisen und ihn dann ausführen.

Erstellen von Lambda-Ausdrücken

Sehen wir uns nun einige Beispiele für Lambda-Ausdrücke an. Im folgenden Code haben wir einen Lambda-Ausdruck ohne Parameter erstellt und ihm eine variable message zugewiesen. Anschließend haben wir den Lambda-Ausdruck durch Aufrufen von message() ausgeführt.

Sehen wir uns auch an, wie Sie Parameter in einen Lambda-Ausdruck einschließen.

Im obigen Code haben wir einen Lambda-Ausdruck mit dem Parameter myString zusammen mit dem Parametertyp String erstellt. Wie Sie sehen, befindet sich vor dem Parametertyp ein Pfeil: Dieser bezieht sich auf den Lambda-Körper. Mit anderen Worten, dieser Pfeil trennt die Parameterliste vom Lambda-Body. Um es übersichtlicher zu gestalten, können wir den Parametertyp (der bereits vom Compiler abgeleitet wurde) vollständig ignorieren.

Um mehrere Parameter zu haben, trennen wir sie einfach durch ein Komma. Und denken Sie daran, dass wir die Parameterliste nicht wie in Java in Klammern setzen.

Beachten Sie jedoch, dass die Parametertypen, die nicht abgeleitet werden können, explizit angegeben werden müssen (wie in diesem Beispiel), da sonst der Code nicht kompiliert wird.

Lambdas an Funktionen übergeben

Wir können Lambda-Ausdrücke als Parameter an Funktionen übergeben: Diese werden "Funktionen höherer Ordnung" genannt, weil sie Funktionen von Funktionen sind. Diese Arten von Funktionen können als Parameter ein Lambda oder eine anonyme Funktion akzeptieren, zum Beispiel die Sammlungsfunktion last().

Im folgenden Code haben wir einen Lambda-Ausdruck an die last()-Funktion übergeben. (Wenn Sie eine Auffrischung der Sammlungen in Kotlin wünschen, besuchen Sie das dritte Tutorial in dieser Reihe) Wie der Name schon sagt, gibt es das letzte Element in der Liste zurück. last() akzeptiert einen Lambda-Ausdruck als Parameter, und dieser Ausdruck verwendet wiederum ein Argument vom Typ String. Sein Funktionsrumpf dient als Prädikat für die Suche innerhalb einer Teilmenge von Elementen in der Auflistung. Das bedeutet, dass der Lambda-Ausdruck entscheidet, welche Elemente der Sammlung bei der Suche nach dem letzten berücksichtigt werden.

Sehen wir uns an, wie Sie die letzte Codezeile oben lesbarer machen können.

Mit dem Kotlin-Compiler können wir die Funktionsklammern entfernen, wenn das letzte Argument in der Funktion ein Lambda-Ausdruck ist. Wie Sie im obigen Code sehen können, durften wir dies tun, da das letzte und einzige an die last()-Funktion übergebene Argument ein Lambda-Ausdruck ist.

Darüber hinaus können wir es prägnanter machen, indem wir den Parametertyp entfernen.

Wir müssen den Parametertyp nicht explizit angeben, da der Parametertyp immer derselbe wie der Sammlungselementtyp ist. Im obigen Code rufen wir last eine Listensammlung von String-Objekten auf, sodass der Kotlin-Compiler intelligent genug ist, um zu wissen, dass der Parameter auch ein String-Typ ist.

Der it-Argumentname

Wir können den Lambda-Ausdruck sogar noch einmal weiter vereinfachen, indem wir das Lambda-Ausdrucksargument durch das automatisch generierte Standardargument name it ersetzen.

Der Name des it-Arguments wurde automatisch generiert, da last einen Lambda-Ausdruck oder eine anonyme Funktion (dazu kommen wir gleich) mit nur einem Argument akzeptieren kann und sein Typ vom Compiler abgeleitet werden kann.

Lokale Rückgabe in Lambda-Ausdrücken

Beginnen wir mit einem Beispiel. Im folgenden Code übergeben wir einen Lambda-Ausdruck an die foreach()-Funktion, die in der intList-Auflistung aufgerufen wird. Diese Funktion durchläuft die Sammlung und führt das Lambda für jedes Element in der Liste aus. Wenn ein Element durch 2 teilbar ist, stoppt es und kehrt vom Lambda zurück.

Wenn Sie den obigen Code ausführen, erhalten Sie möglicherweise nicht das erwartete Ergebnis. Dies liegt daran, dass diese return-Anweisung nicht vom Lambda zurückkehrt, sondern von der enthaltenden Funktion surroundingFunction()! Dies bedeutet, dass die letzte Code-Anweisung in der surroundingFunction() nicht ausgeführt wird.

Um dieses Problem zu beheben, müssen wir ihm explizit mitteilen, von welcher Funktion zurückgekehrt werden soll, indem ein Label oder ein Namens-Tag verwendet wird.

Im obigen aktualisierten Code haben wir das Standard-Tag @forEach direkt nach dem Schlüsselwort return innerhalb des Lambda angegeben. Wir haben jetzt den Compiler angewiesen, anstelle der enthaltenden Funktion surroundingFunction() vom Lambda zurückzukehren. Jetzt wird die letzte Anweisung von surroundingFunction() ausgeführt.

Beachten Sie, dass wir auch unser eigenes Label oder Namensschild definieren können.

Im obigen Code haben wir unser benutzerdefiniertes Label namens myLabel@ definiert und es dann für das Schlüsselwort return angegeben. Das vom Compiler für die forEach-Funktion generierte @forEach-Label ist nicht mehr verfügbar, da wir unser eigenes definiert haben.

Sie werden jedoch bald sehen, wie dieses lokale Rückgabeproblem ohne Labels gelöst werden kann, wenn wir in Kürze anonyme Funktionen in Kotlin besprechen.

3. Mitgliedsfunktionen

Diese Art von Funktion wird innerhalb einer Klasse, eines Objekts oder einer Schnittstelle definiert. Die Verwendung von Memberfunktionen hilft uns, unsere Programme weiter zu modularisieren. Sehen wir uns nun an, wie Sie eine Memberfunktion erstellen.

Dieser Codeausschnitt zeigt eine Klasse Circle (wir werden Kotlin-Klassen in späteren Beiträgen behandeln), die eine Member-Funktion calculateArea() hat. Diese Funktion verwendet einen Parameter radius, um die Fläche eines Kreises zu berechnen.

Um eine Memberfunktion aufzurufen, verwenden wir den Namen der enthaltenden Klasse oder Objektinstanz mit einem Punkt, gefolgt vom Funktionsnamen und übergeben gegebenenfalls Argumente.

4. Anonyme Funktionen

Eine anonyme Funktion ist eine weitere Möglichkeit, einen Codeblock zu definieren, der an eine Funktion übergeben werden kann. Es ist an keine Kennung gebunden. Hier sind die Merkmale einer anonymen Funktion in Kotlin:

  • hat keinen Namen
  • wird mit dem Schlüsselwort fun erstellt
  • enthält einen Funktionskörper

Da wir oben ein Lambda an die last()-Funktion übergeben haben, können wir den Rückgabetyp nicht explizit angeben. Um den Rückgabetyp explizit zu machen, müssen wir stattdessen eine anonyme Funktion verwenden.

Im obigen Code haben wir den Lambda-Ausdruck durch eine anonyme Funktion ersetzt, da wir den Rückgabetyp explizit angeben möchten.

Gegen Ende des Lambda-Abschnitts in diesem Tutorial haben wir ein Label verwendet, um anzugeben, von welcher Funktion zurückgegeben werden soll. Die Verwendung einer anonymen Funktion anstelle eines Lambda innerhalb der forEach()-Funktion löst dieses Problem einfacher. Der Rückgabeausdruck kehrt von der anonymen Funktion zurück und nicht von der umgebenden Funktion, die in unserem Fall surroundingFunction() ist.

5. Lokale oder verschachtelte Funktionen

Um die Modularisierung des Programms weiter voranzutreiben, stellt Kotlin uns lokale Funktionen zur Verfügung, die auch als verschachtelte Funktionen bezeichnet werden. Eine lokale Funktion ist eine Funktion, die in einer anderen Funktion deklariert ist.

Wie Sie im obigen Codeausschnitt sehen können, haben wir zwei einzeilige Funktionen: calCircumference() und calArea(), die in der Funktion printCircumferenceAndAread() verschachtelt sind. Die verschachtelten Funktionen können nur innerhalb der einschließenden Funktion aufgerufen werden und nicht außerhalb. Auch hier macht die Verwendung von verschachtelten Funktionen unser Programm modularer und aufgeräumter.

Wir können unsere lokalen Funktionen prägnanter gestalten, indem wir ihnen nicht explizit Parameter übergeben. Dies ist möglich, da lokale Funktionen Zugriff auf alle Parameter und Variablen der einschließenden Funktion haben. Sehen wir uns das jetzt in Aktion an:

Wie Sie sehen können, sieht dieser aktualisierte Code besser lesbar aus und reduziert das Rauschen, das wir zuvor hatten. Obwohl die einschließende Funktion in diesem Beispiel klein ist, kann diese Funktion in einer größeren einschließenden Funktion, die in kleinere verschachtelte Funktionen unterteilt werden kann, wirklich nützlich sein.

6. Infix-Funktionen

Die infix-Notation ermöglicht es uns, eine Elementfunktion mit einem Argument oder eine Erweiterungsfunktion einfach aufzurufen. Abgesehen davon, dass eine Funktion aus einem Argument besteht, müssen Sie die Funktion auch mit dem infix-Modifikator definieren. Um eine infix-Funktion zu erstellen, sind zwei Parameter beteiligt. Der erste Parameter ist das Zielobjekt, während der zweite Parameter nur ein einzelner Parameter ist, der an die Funktion übergeben wird.

Erstellen einer Infix-Member-Funktion

Schauen wir uns an, wie eine Infix-Funktion in einer Klasse erstellt wird. Im folgenden Codebeispiel haben wir eine Student-Klasse mit einem veränderlichen kotlinScore-Instanzfeld erstellt. Wir haben eine Infix-Funktion erstellt, indem wir den infix-Modifizierer vor dem fun-Schlüsselwort verwendet haben. Wie Sie unten sehen können, haben wir eine Infix-Funktion addKotlinScore() erstellt, die eine Punktzahl erfasst und dem kotlinScore-Instanzfeld hinzufügt.

Aufruf einer Infix-Funktion

Sehen wir uns auch an, wie die von uns erstellte Infix-Funktion aufgerufen wird. Um eine Infix-Funktion in Kotlin aufzurufen, müssen wir nicht die Punktnotation verwenden und den Parameter nicht in Klammern umschließen.

Im obigen Code haben wir die Infix-Funktion aufgerufen, das Zielobjekt ist student, und die doppelten 95.00 ist der Parameter, der an die Funktion übergeben wird.

Die kluge Verwendung von Infix-Funktionen kann unseren Code ausdrucksvoller und klarer machen als der normale Stil. Dies wird beim Schreiben von Unit-Tests in Kotlin sehr geschätzt (wir werden das Testen in Kotlin in einem zukünftigen Beitrag besprechen).

Die Funktion to Infixieren

In Kotlin können wir die Erstellung einer Pair-Instanz prägnanter gestalten, indem wir anstelle des Pair-Konstruktors die Funktion to infix verwenden. (Hinter den Kulissen erstellt to auch eine Pair-Instanz.) Beachten Sie, dass die to-Funktion auch eine Erweiterungsfunktion ist (wir werden diese im nächsten Beitrag genauer besprechen).

Vergleichen wir nun die Erstellung einer Pair-Instanz mit der Funktion to infix und direkt mit dem Pair-Konstruktor, der dieselbe Operation ausführt, und sehen, welche besser ist.

Wie Sie im obigen Code sehen können, ist die Verwendung der Funktion to infix prägnanter als die direkte Verwendung des Pair-Konstruktors zum Erstellen einer Pair-Instanz. Denken Sie daran, dass bei Verwendung der Funktion to infix 234 das Zielobjekt und der String "Nigeria" der an die Funktion übergebene Parameter ist. Beachten Sie außerdem, dass wir dies auch tun können, um einen Pair typ zu erstellen:

Im Beitrag Bereiche und Sammlungen haben wir eine Kartensammlung in Kotlin erstellt, indem wir ihr eine Liste von Paaren gegeben haben – der erste Wert ist der Schlüssel und der zweite der Wert. Vergleichen wir auch die Erstellung einer Karte, indem wir sowohl die to infix-Funktion als auch den Pair-Konstruktor verwenden, um die einzelnen Paare zu erstellen.

Im obigen Code haben wir mit der Funktion to infix eine durch Kommas getrennte Liste von Pair-Typen erstellt und diese an die Funktion mapOf() übergeben. Wir können dieselbe Karte auch erstellen, indem wir den Pair-Konstruktor für jedes Paar direkt verwenden.

Wie Sie erneut sehen können, verursacht das Festhalten an der Funktion to infix weniger Lärm als die Verwendung des Pair-Konstruktors.

Abschluss

In diesem Tutorial haben Sie einige der coolen Dinge kennengelernt, die Sie mit Funktionen in Kotlin machen können. Wir haben abgedeckt:

  • Funktionen auf höchstem Niveau
  • Lambda-Ausdrücke oder Funktionsliterale
  • Mitgliedsfunktionen
  • anonyme Funktionen
  • lokale oder verschachtelte Funktionen
  • Infix-Funktionen

Aber das ist nicht alles! Es gibt noch mehr über die Funktionen in Kotlin zu erfahren. Im nächsten Beitrag lernen Sie einige fortgeschrittene Verwendungen von Funktionen kennen, z. B. Erweiterungsfunktionen, Funktionen höherer Ordnung und Closures. Bis bald!

Um mehr über die Kotlin-Sprache zu erfahren, empfehle ich den Besuch der Kotlin-Dokumentation. Oder sieh dir einige unserer anderen Beiträge zur Entwicklung von Android-Apps hier auf Envato Tuts+ an!

Advertisement
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.