ProgrammationDéveloppeur Kotlin

Comment les fonctions d'ordre supérieur et les expressions lambda sont-elles mises en œuvre en Kotlin ? Décrivez les nuances de la transmission et du retour des fonctions, les particularités de la syntaxe, les principales limitations et fournissez des exemples de code.

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Les fonctions d'ordre supérieur sont des fonctions qui prennent d'autres fonctions comme paramètres ou les retournent. Kotlin utilise des expressions lambda pour transmettre facilement des comportements comme des valeurs.

Exemple de déclaration :

fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int { return operation(a, b) } val sum = operateOnNumbers(3, 2) { x, y -> x + y } // sum = 5

Transmission des fonctions :

  • Les fonctions peuvent être transmises non seulement sous forme d'expressions lambda, mais aussi par référence : :
fun multiply(x: Int, y: Int) = x * y operateOnNumbers(2, 3, ::multiply)

Retour de fonction :

fun makeMultiplier(factor: Int): (Int) -> Int = { x -> x * factor } val triple = makeMultiplier(3) val result = triple(10) // 30

Particularités :

  • Une lambda peut avoir au maximum un paramètre non nommé (it).
  • Des paramètres nommés peuvent être transmis à la fonction, mais les types doivent être explicitement indiqués si le type ne peut pas être déduit.
  • Les expressions lambda sont des objets (classes anonymes), ce qui affecte les performances lors d'appels fréquents dans des boucles chaudes (résolu avec des fonctions inline).
  • Pour les lambdas capturant des variables, on utilise des closures (fermetures).

Une question piégeuse.

Quelle est la différence entre la déclaration du type de fonction (Int, Int) -> Int et l'utilisation du type Function2<Int, Int, Int> ?

Réponse : La syntaxe (Int, Int) -> Int est simplement une déclaration "plus jolie" (sucre syntaxique) pour l'interface Function2<Int, Int, Int>. En pratique, les deux options sont totalement interchangeables.

val f1: (Int, Int) -> Int = { x, y -> x + y } val f2: Function2<Int, Int, Int> = { x, y -> x + y }

Mais la première option est généralement préférable pour sa lisibilité.

Exemples d'erreurs réelles dues à une méconnaissance des subtilités du sujet.


Histoire

Dans un grand système de traitement d'événements, des dizaines d'expressions lambda ont été créées à l'intérieur d'une boucle sans utiliser de fonction inline. Cela a provoqué une forte charge sur le GC et une dégradation des performances, car un objet de fonction anonyme séparé était créé pour chaque appel.


Histoire

Lors de la tentative de retourner une fonction d'une autre fonction, la signature de la fonction de retour n'a pas été correctement spécifiée, ce qui a entraîné une erreur de compilation et une longue recherche de la cause. L'erreur était due à l'absence de parenthèses dans le type : fun foo(): Int -> Int au lieu de fun foo(): (Int) -> Int correcte.


Histoire

Un développeur a tenté d'utiliser une lambda sans type explicitement indiqué comme paramètre d'une autre fonction avec un type non déduit, ce qui a entraîné l'erreur "cannot infer a type for this parameter". Le problème a été résolu en indiquant explicitement le type de la lambda ou du paramètre de la fonction.