Méthodes d’extension en Kotlin

Méthodes d'extension à Kotlin

1. introduction

Kotlin introduit le concept des méthodes d'extension - qui sonta handy way of extending existing classes with a new functionality without using inheritance or any forms of the Decorator pattern - après avoir défini une extension. nous pouvons essentiellement l'utiliser - car c'était la partie de l'API d'origine.

Cela peut être très utile pour rendre notre code facile à lire et à maintenir, car nous sommes en mesure d'ajouter des méthodes spécifiques à nos besoins et de les faire apparaître comme faisant partie du code d'origine, même lorsque nous n'avons pas accès à les sources.

Par exemple, nous pourrions avoir besoin d’effectuer un échappement XML sur unString. Dans le code Java standard, nous aurions besoin d’écrire une méthode qui puisse effectuer cette opération et de l’appeler:

String escaped = escapeStringForXml(input);

Alors que l’écrit en kotlin, l’extrait pourrait être remplacé par:

val escaped = input.escapeForXml()

Non seulement cela est plus facile à lire, mais les IDE pourront proposer la méthode comme option de saisie semi-automatique de la même manière que s'il s'agissait d'une méthode standard sur la classeString.

2. Méthodes d'extension de bibliothèque standard

La bibliothèque standard Kotlin est livrée avec certaines méthodes d'extension prêtes à l'emploi.

2.1. Contexte Ajustement des méthodes d'extension

Some generic extensions exist and can be applied to all types in our application. Celles-ci peuvent être utilisées pour garantir que le code est exécuté dans un contexte approprié et, dans certains cas, pour s’assurer qu’une variable n’est pas nulle.

Il s'avère que, très probablement, nous exploitons les extensions sans nous en rendre compte.

L'une des plus populaires est probablement la méthodelet(), qui peut être appelée sur n'importe quel type dans Kotlin - passons-lui une fonction qui sera exécutée sur la valeur initiale:

val name = "example"
val uppercase = name
  .let { n -> n.toUpperCase() }

C'est similaire à la méthodemap() des classesOptional ouStream - dans ce cas, nous passons une fonction représentant une action qui convertit unString donné en sa représentation en majuscules .

The variable name is known as the receiver of the call car c'est la variable sur laquelle la méthode d'extension agit.

Cela fonctionne très bien avec l'opérateur Safe-Call:

val name = maybeGetName()
val uppercase = name?.let { n -> n.toUpperCase() }

In this case, the block passed to let() is only evaluated if the variable name was non-null. Cela signifie qu'à l'intérieur du bloc, la valeurn est garantie non nulle. Plus d'informations sur cehere.

Il existe d'autres alternatives àlet() qui peuvent également être utiles, en fonction de nos besoins.

L'extensionrun() fonctionne de la même manière quelet(), mais un récepteur est fourni comme valeurthis à l'intérieur du bloc appelé:

val name = "example"
val uppercase = name.run { toUpperCase() }

apply() works the same as run(), but it returns a receiver instead of returning the value from the provided block.

Profitons deapply() pour enchaîner les appels liés:

val languages = mutableListOf()
languages.apply {
    add("Java")
    add("Kotlin")
    add("Groovy")
    add("Python")
}.apply {
    remove("Python")
}

Remarquez comment notre code devient plus concis et expressif sans avoir à utiliser explicitementthis ouit.

L'extensionThe also() fonctionne exactement commelet(), mais elle renvoie le récepteur de la même manière queapply():

val languages = mutableListOf()
languages.also { list ->
    list.add("Java")
    list.add("Kotlin")
    list.add("Groovy")
}

The takeIf() extension is provided with a predicate acting on the receiver, and if this predicate returns true then it returns the receiver ounull sinon - cela fonctionne de manière similaire à une combinaison d'une carte commune des méthodes() etfilter():

val language = getLanguageUsed()
val coolLanguage = language.takeIf { l -> l == "Kotlin" }

L'extensionThe takeUnless() est la même quetakeIf() mais avec la logique de prédicat inversée.

val language = getLanguageUsed()
val oldLanguage = language.takeUnless { l -> l == "Kotlin" }

2.2. Méthodes d'extension pour les collections

Kotlin adds a large number of extension methods to the standard Java Collections which can make our code easier to work with.

Ces méthodes sont situées dansCollections.kt,_ Ranges.kt_ etSequences.kt_, as well as Arrays.kt_ pour que les méthodes équivalentes s'appliquent à la place àArrays. (Rappelez-vous que, dans Kotlin,Arrays peut être traité de la même manière queCollections)

Il y a beaucoup trop de ces méthodes d'extension à discuter ici, alors parcourez ces fichiers pour voir ce qui est disponible.

In addition to Collections, Kotlin adds a significant number of extension methods to the String class - défini enStrings.kt_. These allow us to treat Strings as if they were collections of characters .__

Toutes ces méthodes d'extension fonctionnent ensemble pour nous permettre d'écrire un code nettement plus propre et plus facile à maintenir, quel que soit le type de collection avec lequel nous travaillons.

3. Rédaction de nos méthodes d'extension

Alors, que se passe-t-il si nous devons étendre une classe avec une nouvelle fonctionnalité - soit à partir de la bibliothèque standard Java ou Kotlin ou à partir d'une bibliothèque dépendante que nous utilisons?

Extension methods are written as any other method, mais la classe du récepteur est fournie dans le cadre du nom de la fonction, séparée par le point.

Par exemple:

fun String.escapeForXml() : String {
    ....
}

Cela définira une nouvelle fonction appeléeescapeForXml comme une extension de la classeString, nous permettant de l'appeler comme décrit ci-dessus.

Dans cette fonction, nous pouvons accéder au récepteur en utilisantthis, comme si nous avions écrit ceci dans la classeString elle-même:

fun String.escapeForXml() : String {
  return this
    .replace("&", "&")
    .replace("<", "<")
    .replace(">", ">")
}

3.1. Écriture de méthodes d'extension génériques

Que faire si nous voulons écrire une méthode d'extension destinée à être appliquée à plusieurs types, de manière générique? Nous pourrions simplement étendre le typeAny, - qui est l'équivalent de la classeObject en Java - mais il existe un meilleur moyen.

Extension methods can be applied to a generic receiver as well as a concrete one:

fun  T.concatAsString(b: T) : String {
    return this.toString() + b.toString()
}

Cela pourrait être appliqué à tout type qui répond aux exigences génériques, et à l'intérieur de la valeur de la fonctionthis est typeafe.

Par exemple, en utilisant l'exemple ci-dessus:

5.concatAsString(10) // compiles
"5".concatAsString("10") // compiles
5.concatAsString("10") // doesn't compile

3.2. Écriture de méthodes d'extension Infix

Les méthodes Infix sont utiles pour écrire du code de style DSL, car elles permettent d'appeler des méthodes sans le point ou les crochets:

infix fun Number.toPowerOf(exponent: Number): Double {
    return Math.pow(this.toDouble(), exponent.toDouble())
}

Nous pouvons maintenant appeler cela la même chose que n'importe quelle autre méthode infix:

3 toPowerOf 2 // 9
9 toPowerOf 0.5 // 3

3.3. Écriture de méthodes d'extension d'opérateur

Nous pourrions aussi écrire une méthode d'opérateur en tant qu'extension.

Operator methods are ones that allow us to take advantage of the operator shorthand instead of the full method name - par exemple, la méthode d'opérateurplus peut être appelée à l'aide de l'opérateur+:

operator fun List.times(by: Int): List {
    return this.map { it * by }
}

Encore une fois, cela fonctionne comme n'importe quelle autre méthode d'opérateur:

listOf(1, 2, 3) * 4 // [4, 8, 12]

4. Appel de la fonction d'extension Kotlin à partir de Java

Voyons maintenant comment Java fonctionne avec les fonctions d’extension Kotlin.

In general, every extension method we define in Kotlin is available for us to use in Java. Nous devons cependant nous rappeler que la méthodeinfix doit toujours être appelée avec un point et des parenthèses. Idem avec les extensions d'opérateur - nous ne pouvons pas utiliser uniquement le caractère plus (+). Ces fonctionnalités ne sont disponibles qu'enKotlin.

Cependant, nous ne pouvons pas appeler certaines des méthodes standard de la bibliothèque Kotlin en Java, commelet ouapply, car elles sont marquées avec@InlineOnly.

4.1. Visibilité de la fonction d'extension personnalisée en Java

Utilisons l'une des fonctions d'extension précédemment définies -String.escapeXml(). Notre fichier contenant la méthode d'extension s'appelleStringUtil.kt.

Maintenant, lorsque nous devons appeler une méthode d'extension depuis Java, nous devons utiliser un nom de classeStringUtilKt. Note that we have to add the Kt suffix:

String xml = "hi";

String escapedXml = StringUtilKt.escapeForXml(xml);

assertEquals("hi", escapedXml);

Veuillez faire attention au premier paramètreescapeForXml. Cet argument supplémentaire est un type de récepteur de fonction d'extension. Kotlin with top-level extension function is a pure Java class with a static method. C'est pourquoi il doit en quelque sorte passer lesString. d'origine

Et bien sûr, tout comme dansJava,, nous pouvons utiliser l'importation statique:

import static com.example.kotlin.StringUtilKt.*;

4.2. Appel d'une méthode d'extension Kotlin intégrée

Kotlin nous aide à écrire du code plus facilement et plus rapidement en fournissant de nombreuses fonctions d'extensions intégrées. Par exemple, il existe la méthodeString.capitalize(), qui peut être appelée directement à partir deJava:

String name = "john";

String capitalizedName = StringsKt.capitalize(name);

assertEquals("John", capitalizedName);

Cependant,we can’t call extension methods marked with @InlineOnly from Java, for example:

inline fun  T.let(block: (T) -> R): R

4.3. Renommer la classe statique Java générée

On sait déjà qu'une fonction de sextensionKotlin est une méthode statiqueJava. Renommons une classeJava générée avec une annotation@file:JvmName(name: String).

Ceci doit être ajouté en haut du fichier:

@file:JvmName("Strings")
package com.example.kotlin

fun String.escapeForXml() : String {
    return this
      .replace("&", "&")
      .replace("<", "<")
      .replace(">", ">")
}

Maintenant, lorsque nous voulons appeler une méthode d'extension, nous devons simplement ajouter le nom de la classeStrings:

Strings.escapeForXml(xml);

De plus, nous pouvons toujours ajouter une importation statique:

import static com.example.kotlin.Strings.*;

5. Sommaire

Les méthodes d'extension sont des outils utiles pour étendre des types qui existent déjà dans le système, soit parce qu'elles ne disposent pas des fonctionnalités dont nous avons besoin, soit simplement pour faciliter la gestion d'une zone de code spécifique.

Nous avons vu ici quelques méthodes d'extension prêtes à être utilisées dans le système. De plus, nous avons exploré diverses possibilités de méthodes d’extension. Quelques exemples de cette fonctionnalité peuvent être trouvésover on GitHub.