Kotlin From Scratch Fonctions avancées

Kotlin est un langage fonctionnel, ce qui signifie que les fonctions sont au premier plan. Le langage est rempli de fonctionnalités pour rendre les fonctions de codage faciles et expressives. Dans cet article, vous en apprendrez plus sur les fonctions d'extension, les fonctions d'ordre supérieur, les fermetures et les fonctions en ligne dans Kotlin..

Dans l'article précédent, vous avez découvert les fonctions de niveau supérieur, les expressions lambda, les fonctions anonymes, les fonctions locales, les fonctions infixes et enfin les fonctions membres de Kotlin. Dans ce tutoriel, nous allons continuer à en apprendre davantage sur les fonctions de Kotlin en fouillant dans:

  • fonctions d'extension 
  • fonctions d'ordre supérieur
  • fermetures
  • fonctions en ligne

1. Fonctions d'extension 

Ce ne serait pas bien si le Chaîne tapez Java avait une méthode pour mettre en majuscule la première lettre d'un Chaîne-comme ucfirst () en PHP? On pourrait appeler cette méthode upperCaseFirstLetter ()

Pour réaliser cela, vous pouvez créer un Chaîne sous-classe qui étend la Chaîne tapez en Java. Mais rappelez-vous que le Chaîne La classe en Java est finale, ce qui signifie que vous ne pouvez pas l'étendre. Une solution possible pour Kotlin serait de créer des fonctions d'assistance ou des fonctions de niveau supérieur, mais cela pourrait ne pas être idéal car nous ne pouvions alors pas utiliser la fonctionnalité de saisie semi-automatique de l'EDI pour afficher la liste des méthodes disponibles pour le programme. Chaîne type. Ce qui serait vraiment bien serait d’ajouter en quelque sorte une fonction à une classe sans avoir à hériter de cette classe.

Eh bien, Kotlin nous a couvert avec encore une autre fonctionnalité géniale: fonctions d'extension. Celles-ci nous permettent d'étendre une classe avec de nouvelles fonctionnalités sans avoir à hériter de cette classe. En d'autres termes, il n'est pas nécessaire de créer un nouveau sous-type ou de modifier le type d'origine. 

Une fonction d'extension est déclarée en dehors de la classe qu'elle veut étendre. En d'autres termes, il s'agit également d'une fonction de niveau supérieur (si vous souhaitez un rappel des fonctions de niveau supérieur dans Kotlin, consultez le didacticiel Plus de plaisir avec des fonctions de cette série).. 

Outre les fonctions d'extension, Kotlin prend également en charge propriétés d'extension. Dans cet article, nous allons discuter des fonctions d'extension et attendre un prochain article pour discuter des propriétés d'extension avec les classes dans Kotlin.. 

Créer une fonction d'extension

Comme vous pouvez le constater dans le code ci-dessous, nous avons défini comme normal une fonction de niveau supérieur permettant de déclarer une fonction d'extension. Cette fonction d'extension est à l'intérieur d'un paquet nommé com.chike.kotlin.strings

Pour créer une fonction d'extension, vous devez préfixer le nom de la classe que vous étendez avant le nom de la fonction. Le nom de classe ou le type sur lequel l’extension est définie est appelé le type de récepteur, et le objet récepteur est l'instance ou la valeur de la classe sur laquelle la fonction d'extension est appelée.

package com.chike.kotlin.strings fun String.upperCaseFirstLetter (): String retourne this.substring (0, 1) .toUpperCase (). plus (this.substring (1))

Notez que le ce le mot clé à l'intérieur du corps de la fonction fait référence à l'objet ou à l'instance destinataire. 

Appeler une fonction de poste

Après avoir créé votre fonction d'extension, vous devez d'abord importer la fonction d'extension dans d'autres packages ou fichiers à utiliser dans ce fichier ou package. Ensuite, appeler la fonction revient à appeler n'importe quelle autre méthode de la classe de type de récepteur.

package com.chike.kotlin.packagex import com.chike.kotlin.strings.upperCaseFirstLetter print ("chike" .upperCaseFirstLetter ()) // "Chike"

Dans l'exemple ci-dessus, le type de récepteur est classe Chaîne, et le objet récepteur est "chike". Si vous utilisez un environnement de développement intégré tel que IntelliJ IDEA doté de la fonction IntelliSense, votre nouvelle fonction d’extension est suggérée dans la liste des autres fonctions d’un objet. Chaîne type. 

Interopérabilité Java

Notez que dans les coulisses, Kotlin créera une méthode statique. Le premier argument de cette méthode statique est l'objet récepteur. Il est donc facile pour les appelants Java d'appeler cette méthode statique, puis de passer l'objet récepteur en argument.. 

Par exemple, si notre fonction d’extension a été déclarée dans un StringUtils.kt fichier, le compilateur Kotlin créerait une classe Java StringUtilsKt avec une méthode statique upperCaseFirstLetter ()

/ * Java * / package com.chike.kotlin.strings classe publique StringUtilsKt chaîne publique statique upperCaseFirstLetter (String str) return str.substring (0, 1) .toUpperCase () + str.substring (1); 

Cela signifie que les appelants Java peuvent simplement appeler la méthode en référençant sa classe générée, comme pour toute autre méthode statique.. 

/ * Java * / print (StringUtilsKt.upperCaseFirstLetter ("chike")); // "Chike"

N'oubliez pas que ce mécanisme d'interopérabilité Java est similaire au fonctionnement des fonctions de niveau supérieur dans Kotlin, comme indiqué dans l'article Plus de plaisir avec les fonctions.!

Fonctions d'extension par rapport aux fonctions membres

Notez que les fonctions d'extension ne peuvent pas écraser les fonctions déjà déclarées dans une classe ou une interface connue sous le nom de fonctions membres (si vous souhaitez un rafraîchissement des fonctions membres dans Kotlin, jetez un coup d'œil au didacticiel précédent de cette série). Donc, si vous avez défini une fonction d'extension avec exactement la même signature de fonction - le même nom de fonction et le même nombre, les mêmes types et le même ordre d'arguments, quel que soit le type de retour - le compilateur Kotlin ne l'invoquera pas. Lors du processus de compilation, lorsqu'une fonction est appelée, le compilateur Kotlin recherche d'abord une correspondance dans les fonctions membres définies dans le type d'instance ou dans ses superclasses. S'il existe une correspondance, cette fonction membre est celle qui est invoquée ou liée. S'il n'y a pas de correspondance, le compilateur invoquera n'importe quelle fonction d'extension de ce type.. 

Donc, en résumé: les fonctions membres gagnent toujours. 

Voyons un exemple pratique.

classe Student fun printResult () println ("Impression du résultat de l'élève") fun expel () println ("Expulser l'étudiant de l'école") fun Student.printResult () println ("Fonction d'extension printResult ()")  fun Student.expel (raison: String) println ("Expulser un élève de l’école. Raison: \" $ raison \ "")

Dans le code ci-dessus, nous avons défini un type appelé Étudiant avec deux fonctions membres: printResult () et expulser(). Nous avons ensuite défini deux fonctions d'extension portant les mêmes noms que les fonctions membres. 

Appelons le printResult () fonction et voir le résultat. 

val student = Student () student.printResult () // Impression du résultat de l'élève

Comme vous pouvez le constater, la fonction qui a été appelée ou liée est la fonction membre et non la fonction d'extension avec la même signature de fonction (bien qu'IntelliJ IDEA puisse vous en donner un indice).

Cependant, en appelant la fonction membre expulser() et la fonction d'extension expulser (raison: String) produira des résultats différents parce que les signatures de fonction sont différentes. 

student.expel () // Expulser l'élève de l'école student.expel ("l'argent volé") // Expulser l'élève de l'école. Raison: "volé de l'argent"

Fonctions d'extension des membres

La plupart du temps, vous déclarerez une fonction d'extension en tant que fonction de niveau supérieur, mais notez que vous pouvez également les déclarer en tant que fonctions membres.. 

class ClassB  class ClassA fun ClassB.exFunction () print (toString ()) // appelle ClassB toString () fun callExFunction (classB: ClassB) classB.exFunction () // appelle la fonction d'extension

Dans le code ci-dessus, nous avons déclaré une fonction d'extension exFonction () de ClassB taper dans une autre classe Classe A. le récepteur d'expédition est l'instance de la classe dans laquelle l'extension est déclarée, et l'instance du type de destinataire de la méthode d'extension est appelée la récepteur d'extension. Lorsqu'il y a un conflit de noms ou une ombre entre le récepteur de répartition et le récepteur de poste, notez que le compilateur choisit le récepteur de poste.. 

Donc, dans l'exemple de code ci-dessus, le récepteur d'extension est un exemple de ClassB-cela signifie donc toString () la méthode est de type ClassB quand appelé à l'intérieur de la fonction d'extension exFonction (). Pour nous invoquer le toString () méthode du récepteur d'expédition Classe A au lieu de cela, nous devons utiliser un personnel qualifié ce:

//… fun ClassB.extFunction () print ([email protected] ()) // appelle maintenant la méthode ClassA toString () //… 

2. Fonctions d'ordre supérieur 

Une fonction d'ordre supérieur est simplement une fonction qui prend une autre fonction (ou une expression lambda) en tant que paramètre, renvoie une fonction ou fait les deux. le dernier() fonction de collection est un exemple d'une fonction d'ordre supérieur de la bibliothèque standard. 

val stringList: List = listOf ("in", "the", "club") print (stringList.last it.length == 3) // "le"

Ici nous avons passé un lambda à la dernier fonction servant de prédicat pour rechercher dans un sous-ensemble d’éléments. Nous allons maintenant plonger dans la création de nos propres fonctions d'ordre supérieur dans Kotlin. 

Création d'une fonction d'ordre supérieur

En regardant la fonction circleOperation () ci-dessous, il a deux paramètres. La première, rayon, accepte un double, et le second, op, est une fonction qui accepte un double en entrée et renvoie également un double en sortie. Nous pouvons dire plus succinctement que le deuxième paramètre est "une fonction de double à double". 

Observez que le op les types de paramètre de fonction pour la fonction sont placés entre parenthèses (), et le type de sortie est séparé par une flèche. La fonction circleOperation () est un exemple typique d'une fonction d'ordre supérieur qui accepte une fonction en tant que paramètre.

funCirconférence (rayon: Double) = (2 * Math.PI) * rayon FunCarea (rayon: Double): Double = (Math.PI) * Math.pow (rayon, 2.0) fun circleOperation (rayon: Double, op: (Double) -> Double): Double résultat val = résultat de retour op (rayon)

Invocation d'une fonction d'ordre supérieur

Dans l'invocation de cette circleOperation () fonction, nous passons une autre fonction, calArea (), à cela. (Notez que si la signature de méthode de la fonction transmise ne correspond pas à ce que la fonction d'ordre supérieur déclare, l'appel de la fonction ne sera pas compilé.) 

Pour passer le calArea () fonctionner en tant que paramètre pour circleOperation (), nous devons le préfixer avec :: et omettre le () supports.

print (circleOperation (3.0, :: calArea)) // 28.274333882308138 print (circleOperation (3.0, calArea)) // ne compilera pas print (circleOperation (3.0, calArea ())) // ne compilera pas print (circleOperation ( 6.7, :: calCircumference)) // 42.09734155810323

L'utilisation judicieuse de fonctions d'ordre supérieur peut rendre notre code plus facile à lire et à comprendre.. 

Lambdas et fonctions d'ordre supérieur

Nous pouvons également passer un lambda (ou une fonction littérale) à une fonction d'ordre supérieur directement lors de l'appel de la fonction: 

circleOperation (5.3, (2 * Math.PI) * it)

Rappelez-vous, pour éviter de nommer explicitement l'argument, nous pouvons utiliser le il Le nom de l'argument n'est généré automatiquement pour nous que si le lambda a un argument. (Si vous souhaitez un rappel sur lambda à Kotlin, consultez le didacticiel Plus de plaisir avec ses fonctions de cette série.).

Renvoyer une fonction

N'oubliez pas qu'en plus d'accepter une fonction en tant que paramètre, les fonctions d'ordre supérieur peuvent également renvoyer une fonction aux appelants.. 

multiplicateur amusant (facteur: Double): (Double) -> Double = nombre -> nombre * facteur

Ici le multiplicateur() function retournera une fonction qui applique le facteur donné à tout nombre qui y est passé. Cette fonction renvoyée est un lambda (ou littéral de fonction) de double à double (ce qui signifie que le paramètre d'entrée de la fonction renvoyée est un type double et que le résultat en sortie est également un type double)..  

val doubler = multiplier (2) print (doubler (5.6)) // 11.2

Pour tester cela, nous avons passé un facteur de deux et attribué la fonction renvoyée au doubleur de variable. Nous pouvons appeler cela comme une fonction normale, et quelle que soit la valeur que nous transmettons, elle sera doublée..

3. Fermetures

Une fermeture est une fonction qui a accès aux variables et paramètres définis dans une portée externe.. 

fun printFilteredNamesByLength (length: Int) val names = arrayListOf ("Adam", "Andrew", "Chike", "Kechi") val filterResult = names.filter it.length == longueur println (filterResult) printFilteredNamesByLength ( 5) // [Chike, Kechi]

Dans le code ci-dessus, le lambda est passé à la filtre() fonction de collecte utilise le paramètre longueur de la fonction extérieure printFilteredNamesByLength (). Notez que ce paramètre est défini en dehors du champ d'application du lambda, mais que le lambda est toujours en mesure d'accéder au longueur. Ce mécanisme est un exemple de fermeture en programmation fonctionnelle.

4. Fonctions en ligne

Dans More Fun With Functions, j'ai mentionné que le compilateur Kotlin crée une classe anonyme dans les versions antérieures de Java en coulisse lors de la création d'expressions lambda.. 

Malheureusement, ce mécanisme introduit une surcharge car une classe anonyme est créée sous le capot chaque fois que nous créons un lambda. En outre, un lambda qui utilise le paramètre de fonction externe ou la variable locale avec une fermeture ajoute son propre temps système d’allocation de mémoire, puisqu’un nouvel objet est alloué au segment de mémoire à chaque appel.. 

Comparaison des fonctions en ligne avec des fonctions normales

Pour éviter ces frais généraux, l’équipe de Kotlin nous a fourni le en ligne modificateur pour les fonctions. Une fonction d'ordre supérieur avec le en ligne modificateur sera en ligne lors de la compilation du code. En d’autres termes, le compilateur copiera le lambda (ou fonction littérale) ainsi que le corps de la fonction supérieure et les collera au site d’appel.. 

Regardons un exemple pratique. 

fun circleOperation (rayon: Double, op: (Double) -> Double) println ("Le rayon est $ radius") val result = op (rayon) println ("Le résultat est $ result") fun main (arguments: Array) circleOperation (5.3, (2 * Math.PI) * it) 

Dans le code ci-dessus, nous avons une fonction d'ordre supérieur circleOperation () ça n'a pas le en ligne modificateur. Voyons maintenant le bytecode de Kotlin généré lors de la compilation et de la décompilation du code, puis comparons-le avec celui qui a la valeur en ligne modificateur. 

public final class InlineFunctionKt public final final statique statique CircleOperation (double rayon, @NotNull Function1 op) Intrinsics.checkParameterIsNotNull (op, "op"); String var3 = "Rayon est" + rayon; System.out.println (var3); double résultat = ((Nombre) op.invoke (rayon)). doubleValue (); String var5 = "Le résultat est" + result; System.out.println (var5);  public statique final final principal (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); circleOperation (5.3D, (Function1) null.INSTANCE); 

Dans le bytecode Java généré ci-dessus, vous pouvez voir que le compilateur a appelé la fonction circleOperation () à l'intérieur de principale() méthode.

Spécifions maintenant la fonction d'ordre supérieur comme en ligne à la place, et aussi voir le bytecode généré.

inline fun circleOperation (rayon: Double, op: (Double) -> Double) println ("Le rayon est $ radius") val result = op (rayon) println ("Le résultat est $ result") fun main (arguments: Tableau) circleOperation (5.3, (2 * Math.PI) * it)

Pour créer une fonction d’ordre supérieur en ligne, nous devons insérer le en ligne modificateur avant le amusement mot-clé, comme nous l'avons fait dans le code ci-dessus. Vérifions aussi le bytecode généré pour cette fonction inline. 

circleOpération finale publique statique statique (double rayon, @NotNull Function1 op) Intrinsics.checkParameterIsNotNull (op, "op"); String var4 = "Rayon est" + rayon; System.out.println (var4); double résultat = ((Nombre) op.invoke (rayon)). doubleValue (); String var6 = "Le résultat est" + result; System.out.println (var6);  public statique final final principal (@NotNull String [] args) Intrinsics.checkParameterIsNotNull (args, "args"); double rayon $ iv = 5,3D; String var3 = "Rayon est" + rayon $ iv; System.out.println (var3); résultat double $ iv = 6.283185307179586D * rayon $ iv; String var9 = "Le résultat est" + result $ iv; System.out.println (var9); 

En regardant le bytecode généré pour la fonction inline à l'intérieur du principale() fonction, vous pouvez observer qu’au lieu d’appeler le circleOperation () fonction, il a maintenant copié le circleOperation () corps de fonction comprenant le corps lambda et collé sur son site d'appel.

Avec ce mécanisme, notre code a été optimisé de manière significative: plus de création de classes anonymes ni d’allocations de mémoire supplémentaires. Mais sachez que nous aurions un code plus important dans les coulisses qu'auparavant. Pour cette raison, il est fortement recommandé de ne mettre en ligne que des fonctions plus petites d'ordre supérieur qui acceptent lambda en tant que paramètres.. 

La plupart des fonctions d'ordre supérieur de la bibliothèque standard de Kotlin ont le modificateur inline. Par exemple, si vous jetez un coup d’œil aux fonctions d’opération de collecte filtre() et premier(), vous verrez qu'ils ont le en ligne modificateur et sont également de petite taille. 

plaisir en ligne publique  Iterable.filter (prédicat: (T) -> Boolean): List return filterTo (ArrayList(), prédicat) fun inline public  Iterable.first (prédicat: (T) -> Boolean): T pour (élément dans ceci) if (prédicate (élément)) élément de retour retourné NoSuchElementException ("La collection ne contient aucun élément correspondant au prédicat.")

N'oubliez pas de ne pas inclure les fonctions normales qui n'acceptent pas un lambda en tant que paramètre! Ils compileront, mais il n'y aura pas d'amélioration significative de la performance (IntelliJ IDEA donnerait même un indice à ce sujet).  

le noline Modificateur

Si vous avez plus de deux paramètres lambda dans une fonction, vous avez la possibilité de décider quel lambda ne doit pas être en ligne à l'aide de la commande noline modificateur sur le paramètre. Cette fonctionnalité est particulièrement utile pour un paramètre lambda qui prendra beaucoup de code. En d'autres termes, le compilateur Kotlin ne copie pas et ne colle pas le lambda où il est appelé, mais crée une classe anonyme derrière la scène..  

fun en ligne myFunc (op: (Double) -> Double, noinline op2: (Int) -> Int) // effectuer des opérations

Ici, nous avons inséré le noline modificateur au deuxième paramètre lambda. Notez que ce modificateur n’est valide que si la fonction a la en ligne modificateur.

Traçage de pile dans les fonctions en ligne

Notez que lorsqu'une exception est levée dans une fonction inline, la pile d'appels de méthode dans la trace de pile est différente d'une fonction normale sans le en ligne modificateur. Cela est dû au mécanisme de copier / coller utilisé par le compilateur pour les fonctions en ligne. Le bon côté des choses est qu’IntelliJ IDEA nous aide à naviguer facilement dans la pile d’appels de méthodes dans la trace de pile pour une fonction en ligne. Voyons un exemple.

fun en ligne myFunc (op: (Double) -> Double) une exception ("message 123") fun principale (arguments: Array) myFunc (4.5)

Dans le code ci-dessus, une exception est levée délibérément dans la fonction inline myFunc (). Voyons maintenant la trace de la pile dans IntelliJ IDEA lorsque le code est exécuté. En regardant la capture d'écran ci-dessous, vous pouvez voir que nous avons deux options de navigation à choisir: le corps de la fonction Inline ou le site d'appel de la fonction inline. Le choix de la première nous mènera au point où l'exception a été lancée dans le corps de la fonction, tandis que le dernier nous mènera au point où la méthode s'appel.

Si la fonction n'était pas en ligne, notre trace de pile ressemblerait à celle que vous connaissez déjà:

Conclusion

Dans ce tutoriel, vous avez appris encore plus de choses que vous pouvez faire avec les fonctions de Kotlin. Nous avons couvert:

  • fonctions d'extension
  • fonctions d'ordre supérieur
  • fermetures
  • fonctions en ligne

Dans le prochain didacticiel de la série Kotlin From Scratch, nous allons explorer la programmation orientée objet et commencer à apprendre le fonctionnement des classes dans Kotlin. À bientôt!

Pour en savoir plus sur la langue Kotlin, je vous recommande de consulter la documentation Kotlin. Ou consultez certains de nos autres articles sur le développement d'applications Android ici sur Envato Tuts+!