Kotlin ist eine funktionale Sprache, und das bedeutet, Funktionen stehen im Vordergrund und in der Mitte. Die Sprache ist vollgepackt mit Funktionen, die das Kodieren einfach und ausdrucksstark machen. In diesem Beitrag erfahren Sie mehr über Erweiterungsfunktionen, Funktionen höherer Ordnung, Schließungen und Inline-Funktionen in Kotlin.
Im vorigen Artikel haben Sie die Funktionen der obersten Ebene, Lambda-Ausdrücke, anonyme Funktionen, lokale Funktionen, Infix-Funktionen und schließlich Member-Funktionen in Kotlin kennengelernt. In diesem Lernprogramm erfahren Sie mehr über Funktionen in Kotlin, indem wir uns mit folgenden Themen befassen:
Wäre es nicht schön, wenn die String
Typ in Java hatte eine Methode, um den ersten Buchstaben in einem Großbuchstaben zu schreiben String
-mögen ucfirst ()
in PHP? Wir könnten diese Methode nennen upperCaseFirstLetter ()
.
Um dies zu realisieren, können Sie ein erstellen String
Unterklasse, die die erweitert String
Geben Sie Java ein. Aber denkt daran, dass die String
Klasse in Java ist final - was bedeutet, dass Sie sie nicht erweitern können. Eine mögliche Lösung für Kotlin wäre das Erstellen von Hilfsfunktionen oder Funktionen auf oberster Ebene. Dies ist jedoch möglicherweise nicht ideal, da wir die IDE-Funktion zum automatischen Vervollständigen der IDE nicht verwenden können, um die Liste der verfügbaren Methoden für die String
Art. Was wirklich schön wäre, wäre, einer Klasse irgendwie eine Funktion hinzuzufügen, ohne von dieser Klasse erben zu müssen.
Nun, Kotlin hat uns mit einem weiteren fantastischen Feature abgedeckt: Erweiterungsfunktionen. Dadurch können wir eine Klasse mit neuen Funktionen erweitern, ohne von dieser Klasse erben zu müssen. Mit anderen Worten, wir müssen keinen neuen Untertyp erstellen oder den ursprünglichen Typ ändern.
Eine Erweiterungsfunktion wird außerhalb der Klasse deklariert, die sie erweitern möchte. Mit anderen Worten, es ist auch eine Top-Level-Funktion (wenn Sie eine Auffrischung der Top-Level-Funktionen in Kotlin erhalten möchten, besuchen Sie das Lernprogramm "Mehr Spaß mit Funktionen" in dieser Serie.).
Neben Erweiterungsfunktionen unterstützt Kotlin auch Erweiterungseigenschaften. In diesem Beitrag werden wir Erweiterungsfunktionen diskutieren und bis zu einem zukünftigen Beitrag warten, um die Erweiterungseigenschaften zusammen mit Klassen in Kotlin zu diskutieren.
Wie Sie im folgenden Code sehen können, haben wir normalerweise eine Funktion der obersten Ebene definiert, um eine Erweiterungsfunktion zu deklarieren. Diese Erweiterungsfunktion befindet sich in einem Paket namens com.chike.kotlin.strings
.
Um eine Erweiterungsfunktion zu erstellen, müssen Sie den Namen der Klasse, die Sie erweitern, vor dem Funktionsnamen voranstellen. Der Klassenname oder der Typ, für den die Erweiterung definiert ist, heißt Empfängertyp, und das Empfängerobjekt ist die Klasseninstanz oder der Wert, für den die Erweiterungsfunktion aufgerufen wird.
package com.chike.kotlin.strings fun String.upperCaseFirstLetter (): String return this.substring (0, 1) .toUpperCase (). plus (this.substring (1))
Notiere dass der diese
Das Schlüsselwort im Funktionskörper verweist auf das Empfängerobjekt oder die Instanz.
Nach dem Erstellen der Erweiterungsfunktion müssen Sie zunächst die Erweiterungsfunktion in andere Pakete oder Dateien importieren, die in dieser Datei oder diesem Paket verwendet werden sollen. Das Aufrufen der Funktion ist dann genau dasselbe wie das Aufrufen einer beliebigen anderen Methode der Empfängertypklasse.
Paket com.chike.kotlin.packagex import com.chike.kotlin.strings.upperCaseFirstLetter print ("chike" .upperCaseFirstLetter ()) // "Chike"
Im obigen Beispiel wird der Empfängertyp ist klasse String
, und das Empfängerobjekt ist "chike"
. Wenn Sie eine IDE wie IntelliJ IDEA mit der IntelliSense-Funktion verwenden, wird Ihre neue Erweiterungsfunktion in der Liste der anderen Funktionen in a angezeigt String
Art.
Beachten Sie, dass Kotlin hinter den Kulissen eine statische Methode erstellt. Das erste Argument dieser statischen Methode ist das Empfängerobjekt. Daher ist es für Java-Aufrufer einfach, diese statische Methode aufzurufen und das Empfängerobjekt als Argument zu übergeben.
Zum Beispiel, wenn unsere Erweiterungsfunktion in a deklariert wurde StringUtils.kt Datei erstellt der Kotlin-Compiler eine Java-Klasse StringUtilsKt
mit einer statischen Methode upperCaseFirstLetter ()
.
/ * Java * / package com.chike.kotlin.strings öffentliche Klasse StringUtilsKt public static String upperCaseFirstLetter (String str) return str.substring (0, 1) .toUpperCase () + str.substring (1);
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.
/ * Java * / print (StringUtilsKt.upperCaseFirstLetter ("chike")); // "Chike"
Denken Sie daran, dass dieser Java-Interop-Mechanismus der Funktionsweise von Top-Level-Funktionen in Kotlin ähnelt, wie wir im Beitrag Mehr Spaß mit Funktionen besprochen haben!
Beachten Sie, dass Erweiterungsfunktionen keine Funktionen überschreiben können, die bereits in einer Klasse oder einem Interface deklariert wurden, die als Member-Funktionen bezeichnet wird (wenn Sie eine Auffrischung der Member-Funktionen in Kotlin erhalten möchten, schauen Sie sich das vorherige Tutorial dieser Serie an). Wenn Sie also eine Erweiterungsfunktion mit genau derselben Funktionssignatur definiert haben, nämlich den gleichen Funktionsnamen und die gleiche Anzahl, den gleichen Typ und die Reihenfolge der Argumente, wird der Kotlin-Compiler diese Funktion unabhängig vom Rückgabetyp nicht aufrufen. Während des Kompilierens sucht der Kotlin-Compiler beim Aufrufen einer Funktion zuerst nach einer Übereinstimmung in den Member-Funktionen, die im Instanztyp oder in seinen Superklassen definiert sind. Wenn es eine Übereinstimmung gibt, ist diese Memberfunktion diejenige, die aufgerufen oder gebunden wird. Wenn es keine Übereinstimmung gibt, ruft der Compiler eine Erweiterungsfunktion dieses Typs auf.
Zusammenfassend: Mitgliederfunktionen gewinnen immer.
Sehen wir uns ein praktisches Beispiel an.
Klasse Student fun printResult () println ("Schülerergebnis drucken") fun expel () println ("Schüler aus der Schule ausweisen") fun Student.printResult () println ("Erweiterungsfunktion printResult ()") fun Student.expel (Grund: String) println ("Schüler aus der Schule ausschließen. Grund: \" $ reason \ "")
Im obigen Code haben wir einen Typ definiert Student
mit zwei Mitgliedsfunktionen: printResult ()
und vertreiben()
. Wir haben dann zwei Erweiterungsfunktionen definiert, die denselben Namen wie die Member-Funktionen haben.
Nennen wir das printResult ()
Funktion und sehen das Ergebnis.
val student = Student () student.printResult () // Schülerergebnis drucken
Wie Sie sehen, war die aufgerufene oder gebundene Funktion die Member-Funktion und nicht die Erweiterungsfunktion mit derselben Funktionssignatur (IntelliJ IDEA würde Ihnen trotzdem einen Hinweis geben)..
Aufrufen der Memberfunktion vertreiben()
und die Erweiterungsfunktion ausweisen (Grund: String)
führt zu unterschiedlichen Ergebnissen, da die Funktionssignaturen unterschiedlich sind.
student.expel () // Schüler aus der Schule ausweisen student.expel ("Geld gestohlen") // Schüler aus der Schule ausweisen. Grund: "Geld gestohlen"
Eine Erweiterungsfunktion wird meistens als Top-Level-Funktion deklariert. Beachten Sie jedoch, dass Sie sie auch als Member-Funktionen deklarieren können.
class ClassB class ClassA fun ClassB.exFunction () print (toString ()) // Aufruf von ClassB toString () fun callExFunction (classB: ClassB) classB.exFunction () // Aufruf der Erweiterungsfunktion
Im obigen Code haben wir eine Erweiterungsfunktion deklariert Funktion ()
von Klasse b
tippen Sie in eine andere Klasse Klasse a
. Das Empfänger versenden ist die Instanz der Klasse, in der die Erweiterung deklariert ist, und die Instanz des Empfängertyps der Erweiterungsmethode wird als bezeichnet Erweiterungsempfänger. Beachten Sie, dass der Compiler den Erweiterungsempfänger auswählt, wenn ein Namenskonflikt oder eine Spiegelung zwischen dem Empfangsempfänger und dem Erweiterungsempfänger auftritt.
Also im obigen Codebeispiel Erweiterungsempfänger ist ein Beispiel von Klasse b
-also bedeutet es das toString ()
Methode ist vom Typ Klasse b
wenn innerhalb der Erweiterungsfunktion aufgerufen Funktion ()
. Für uns, um das anzurufen toString ()
Methode der Empfänger versenden Klasse a
stattdessen müssen wir ein qualifiziertes verwenden diese
:
//… fun ClassB.extFunction () print ([email protected] ()) // ruft jetzt die ClassA toString () -Methode auf //…
Eine Funktion höherer Ordnung ist nur eine Funktion, die eine andere Funktion (oder einen Lambda-Ausdruck) als Parameter verwendet, eine Funktion zurückgibt oder beides. Das zuletzt()
Collection-Funktion ist ein Beispiel für eine übergeordnete Funktion aus der Standardbibliothek.
val stringList: Liste= listOf ("in", "the", "club") print (stringList.last it.length == 3) // "der"
Hier haben wir ein Lambda übergeben zuletzt
Funktion, die als Prädikat für die Suche innerhalb einer Teilmenge von Elementen dient. Wir werden jetzt in Kotlin unsere eigenen Funktionen höherer Ordnung erstellen.
Blick auf die Funktion KreisBedienung ()
unten hat es zwei Parameter. Der Erste, Radius
, akzeptiert ein doppeltes und das zweite, op
, ist eine Funktion, die ein double als Eingabe akzeptiert und auch ein double als Ausgabe zurückgibt. Wir können genauer sagen, dass der zweite Parameter "eine Funktion von double nach double" ist..
Beachten Sie, dass die op
Funktionsparametertypen für die Funktion sind in Klammern gesetzt ()
, und der Ausgabetyp wird durch einen Pfeil getrennt. Die Funktion KreisBedienung ()
ist ein typisches Beispiel für eine Funktion höherer Ordnung, die eine Funktion als Parameter akzeptiert.
fun calKreisumfang (Radius: Double) = (2 * Math.PI) * Radius Fun calArea (Radius: Double): Double = (Math.PI) * Math.pow (Radius, 2,0) Fun CircleOperation (Radius: Double, Op: (Double) -> Double): Double val Ergebnis = Ergebnis Ergebnis (Radius)
In der Anrufung dieses KreisBedienung ()
Funktion übergeben wir eine andere Funktion, calArea ()
, dazu (Beachten Sie, dass der Funktionsaufruf nicht kompiliert wird, wenn die Methodensignatur der übergebenen Funktion nicht mit dem übereinstimmt, was die übergeordnete Funktion deklariert.
Um die calArea ()
Funktion als Parameter für KreisBedienung ()
, Wir müssen es mit vorangestellt ::
und das weglassen ()
Klammern.
print (circleOperation (3.0, :: calArea)) // 28.274333882308138 print (circleOperation (3.0, calArea)) // wird nicht kompiliert print (circleOperation (3.0, calArea ())) // wird nicht kompilieren print (circleOperation ( 6.7, :: calCumper)) // 42.09734155810323
Durch die Verwendung von Funktionen höherer Ordnung wird der Code lesbarer und verständlicher.
Wir können ein Lambda (oder ein Funktionsliteral) auch direkt an eine Funktion höherer Ordnung übergeben, wenn Sie die Funktion aufrufen:
circleOperation (5.3, (2 * Math.PI) * it)
Denken Sie daran, um das Argument nicht explizit zu benennen, können wir das verwenden es
Argumentname wird für uns nur automatisch generiert, wenn das Lambda ein Argument hat. (Wenn Sie eine Auffrischung zu Lambda in Kotlin suchen, besuchen Sie das Lernprogramm "Mehr Spaß mit Funktionen" in dieser Serie.).
Denken Sie daran, dass Funktionen mit einer höheren Ordnung nicht nur eine Funktion als Parameter akzeptieren, sondern auch eine Funktion an den Aufrufer zurückgeben können.
Spaßvervielfacher (Faktor: Double): (Double) -> Double = number -> number * factor
Hier die Multiplikator()
function gibt eine Funktion zurück, die den angegebenen Faktor auf eine beliebige Zahl anwendet. Diese zurückgegebene Funktion ist ein Lambda (oder Funktionsliteral) von double bis double (was bedeutet, dass der Eingabeparameter der zurückgegebenen Funktion ein Doppeltyp ist und das Ausgabeergebnis ebenfalls ein Doppeltyp ist.).
Val-Doppler = Multiplikator (2) Druck (Doppler (5.6)) // 11.2
Um dies zu testen, haben wir einen Faktor von zwei übergeben und die zurückgegebene Funktion dem Variablenverdoppler zugewiesen. Wir können dies wie eine normale Funktion aufrufen, und der Wert, den wir in diese Funktion eingeben, wird verdoppelt.
Ein Abschluss ist eine Funktion, die Zugriff auf Variablen und Parameter hat, die in einem äußeren Bereich definiert sind.
fun printFilteredNamesByLength (Länge: Int) val names = arrayListOf ("Adam", "Andrew", "Chike", "Kechi") val filterResult = names.filter it.length == length println (filterResult) printFilteredNamesByLength ( 5) // [Chike, Kechi]
Im obigen Code wurde das Lambda an das übergeben Filter()
Collection-Funktion verwendet den Parameter Länge
der äußeren Funktion printFilteredNamesByLength ()
. Beachten Sie, dass dieser Parameter außerhalb des Geltungsbereichs des Lambda definiert ist, das Lambda jedoch weiterhin auf das zugreifen kann Länge
. Dieser Mechanismus ist ein Beispiel für die Schließung der funktionalen Programmierung.
In More Fun With Functions erwähnte ich, dass der Kotlin-Compiler beim Erstellen von Lambda-Ausdrücken hinter den Kulissen eine anonyme Klasse in früheren Java-Versionen erstellt.
Leider führt dieser Mechanismus zu Overhead, da jedes Mal, wenn wir ein Lambda erstellen, eine anonyme Klasse unter der Haube erstellt wird. Auch ein Lambda, das den äußeren Funktionsparameter oder die lokale Variable mit einer Schließung verwendet, fügt seinen eigenen Speicherzuweisungsaufwand hinzu, da dem Heap bei jedem Aufruf ein neues Objekt zugewiesen wird.
Um diese Gemeinkosten zu vermeiden, hat uns das Kotlin-Team die in der Reihe
Modifikator für Funktionen. Eine übergeordnete Funktion mit der in der Reihe
Modifier wird während der Code-Kompilierung eingebettet. Mit anderen Worten, der Compiler kopiert das Lambda (oder das Funktionsliteral) und den Funktionskörper höherer Ordnung und fügt sie an der Aufrufstelle ein.
Schauen wir uns ein praktisches Beispiel an.
fun circleOperation (radius: Double, op: (Double) -> Double) println ("Radius ist $ radius") val result = op (radius) println ("Das Ergebnis ist $ result") fun main (args: Array) circleOperation (5.3, (2 * Math.PI) * it)
Im obigen Code haben wir eine Funktion höherer Ordnung KreisBedienung ()
das hat nicht die in der Reihe
Modifikator. Nun sehen wir uns den Kotlin-Bytecode an, der beim Kompilieren und Dekompilieren des Codes erzeugt wurde, und vergleicht ihn dann mit einem Code, der den in der Reihe
Modifikator.
public final class InlineFunctionKt public static final void circleOperation (doppelter Radius, @NotNull-Funktion1 op) Intrinsics.checkParameterIsNotNull (op, "op"); String var3 = "Radius ist" + Radius; System.out.println (var3); double result = ((Number) op.invoke (radius)). doubleValue (); String var5 = "Das Ergebnis ist" + Ergebnis; System.out.println (var5); public static final void main (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); circleOperation (5.3D, (Function1) null.INSTANCE);
Im generierten Java-Bytecode oben sehen Sie, dass der Compiler die Funktion aufgerufen hat KreisBedienung ()
in der Main()
Methode.
Lassen Sie uns nun die Funktion höherer Ordnung als angeben in der Reihe
Stattdessen sehen Sie auch den generierten Bytecode.
inline fun circleOperation (radius: Double, op: (Double) -> Double) println ("Radius ist $ radius") val result = op (radius) println ("Das Ergebnis ist $ result") fun main (args: Array) circleOperation (5.3, (2 * Math.PI) * it)
Um eine übergeordnete Funktion inline zu erstellen, müssen Sie die in der Reihe
Modifikator vor dem Spaß
Schlüsselwort, genau wie im obigen Code. Lassen Sie uns auch den für diese Inline-Funktion generierten Bytecode überprüfen.
public static final void circleOperation (doppelter Radius, @NotNull-Funktion1 op) Intrinsics.checkParameterIsNotNull (op, "op"); String var4 = "Radius ist" + Radius; System.out.println (var4); double result = ((Number) op.invoke (radius)). doubleValue (); String var6 = "Das Ergebnis ist" + Ergebnis; System.out.println (var6); public static final void main (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); Doppelradius $ iv = 5,3D; String var3 = "Radius ist" + Radius $ iv; System.out.println (var3); Doppelergebnis $ iv = 6.283185307179586D * Radius $ iv; String var9 = "Das Ergebnis ist" + Ergebnis $ iv; System.out.println (var9);
Betrachten Sie den generierten Bytecode für die Inline-Funktion im Main()
Funktion können Sie das beobachten, anstatt die KreisBedienung ()
Funktion hat es jetzt kopiert KreisBedienung ()
Funktionskörper einschließlich des Lambda-Körpers und klebte ihn an seiner Aufrufstelle ein.
Mit diesem Mechanismus wurde unser Code erheblich optimiert - keine weiteren anonymen Klassen oder zusätzliche Speicherzuweisungen mehr. Seien Sie sich jedoch bewusst, dass sich hinter den Kulissen ein größerer Bytecode befindet als zuvor. Aus diesem Grund wird dringend empfohlen, nur kleinere Funktionen höherer Ordnung zu integrieren, die Lambda als Parameter akzeptieren.
Viele der Funktionen der Standardbibliothek höherer Ordnung in Kotlin verfügen über den Inline-Modifikator. Zum Beispiel, wenn Sie einen Blick auf die Erfassungsoperationsfunktionen werfen Filter()
und zuerst()
, Sie werden sehen, dass sie die haben in der Reihe
Modifier und sind auch klein.
öffentlicher Inline-SpaßIterable .filter (Prädikat: (T) -> Boolean): Liste return filterTo (ArrayList.) (), Prädikat) öffentlicher Inline-Spaß Iterable .first (Prädikat: (T) -> Boolean): T for (Element in this) if (Prädikat (Element)) return element throw NoSuchElementException ("Collection enthält kein dem Prädikat entsprechendes Element.")
Denken Sie daran, keine normalen Funktionen zu integrieren, die kein Lambda als Parameter akzeptieren! Sie werden kompilieren, aber es würde keine signifikante Leistungsverbesserung geben (IntelliJ IDEA würde sogar einen Hinweis darauf geben).
noinline
ModifikatorWenn Sie mehr als zwei Lambda-Parameter in einer Funktion haben, haben Sie die Möglichkeit zu entscheiden, welches Lambda nicht mit der Inline-Funktion inline geschaltet werden soll noinline
Modifikator für den Parameter. Diese Funktionalität ist besonders für Lambda-Parameter hilfreich, die viel Code enthalten. Mit anderen Worten, der Kotlin-Compiler kopiert das Lambda nicht, wo es aufgerufen wird, sondern erstellt eine anonyme Klasse hinter der Szene.
Inline-Spaß myFunc (op: (Double) -> Double, noinline op2: (Int) -> Int) // Operationen ausführen
Hier haben wir die eingefügt noinline
Modifikator für den zweiten Lambda-Parameter. Beachten Sie, dass dieser Modifikator nur gültig ist, wenn die Funktion das hat in der Reihe
Modifikator.
Wenn eine Ausnahme innerhalb einer Inline-Funktion ausgelöst wird, unterscheidet sich der Methodenaufruf-Stack in der Stack-Ablaufverfolgung von einer normalen Funktion ohne in der Reihe
Modifikator. Der Grund hierfür ist der vom Compiler für Inline-Funktionen verwendete Copy & Paste-Mechanismus. Die coole Sache ist, dass IntelliJ IDEA uns hilft, den Methodenaufruf-Stack im Stack-Trace für eine Inline-Funktion zu durchsuchen. Lassen Sie uns ein Beispiel sehen.
inline fun myFunc (op: (Double) -> Double) throw Exception ("message 123") fun main (args: Array)) myFunc (4.5)
Im obigen Code wird absichtlich eine Ausnahme innerhalb der Inline-Funktion ausgelöst myFunc ()
. Lassen Sie uns nun die Stapelverfolgung in IntelliJ IDEA sehen, wenn der Code ausgeführt wird. Wenn Sie den Screenshot unten betrachten, können Sie sehen, dass wir zwei Navigationsoptionen zur Auswahl haben: den Inline-Funktionskörper oder die Inline-Funktionsaufruf-Site. Die Auswahl des ersteren führt uns zu dem Punkt, an dem die Ausnahme in den Funktionskörper geworfen wurde, während der letztere zu dem Punkt führt, in dem die Methode aufgerufen wurde.
Wenn es sich bei der Funktion nicht um eine Inline-Funktion handelt, wäre unser Stack-Trace wie das, mit dem Sie möglicherweise bereits vertraut sind:
In diesem Lernprogramm haben Sie noch mehr gelernt, was Sie mit Funktionen in Kotlin tun können. Wir haben abgedeckt:
Im nächsten Tutorial der Kotlin From Scratch-Reihe werden wir uns mit objektorientierter Programmierung beschäftigen und lernen, wie Klassen in Kotlin funktionieren. Bis bald!
Um mehr über die Kotlin-Sprache zu lernen, empfehle ich die Kotlin-Dokumentation. Auf Envato Tuts finden Sie auch einige unserer anderen Android-Apps zur Entwicklung von Apps+!