Méthodes d'extension dans Kotlin

1. Introduction

Kotlin introduit le concept de méthodes d'extension - qui sont un moyen pratique d'étendre les classes existantes avec une nouvelle fonctionnalité sans utiliser l'héritage ou toute forme du modèle Decorator - 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 une chaîne. Dans le code Java standard, nous aurions besoin d'écrire une méthode qui peut effectuer cela et de l'appeler:

String escaped = escapeStringForXml(input);

Alors qu'il est écrit en Kotlin, l'extrait de code pourrait être remplacé par:

val escaped = input.escapeForXml()

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

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

Certaines extensions génériques existent et peuvent être appliquées à tous les types de notre application . Celles-ci peuvent être utilisées pour garantir que le code est exécuté dans un contexte approprié et, dans certains cas, pour garantir 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 peut-être la méthode let () , 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 = "Baeldung" val uppercase = name .let { n -> n.toUpperCase() }

C'est similaire à la méthode map () des classes Optional ou Stream - dans ce cas, nous transmettons une fonction représentant une action qui convertit un String donné en sa représentation en majuscules.

Le nom de la variable est connu comme le récepteur de l'appel car c'est la variable sur laquelle la méthode d'extension agit.

Cela fonctionne très bien avec l'opérateur d'appel sécurisé:

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

Dans ce cas, le bloc passé à let () n'est évalué que si le nom de la variable n'est pas nul . Cela signifie qu'à l'intérieur du bloc, la valeur n est garantie non nulle. Plus d'informations ici.

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

L' extension run () fonctionne de la même manière que let () , mais un récepteur est fourni comme valeur this dans le bloc appelé:

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

apply () fonctionne de la même manière que run () , mais il renvoie un récepteur au lieu de renvoyer la valeur du bloc fourni .

Profitons de apply () pour les appels liés à la chaîne:

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 explicitement ceci ou cela .

L' extension also () fonctionne exactement comme let () , mais elle renvoie le receveur de la même manière que apply () :

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

L' extension takeIf () est fournie avec un prédicat agissant sur le récepteur, et si ce prédicat retourne true, il retourne le récepteur ou null sinon - cela fonctionne de manière similaire à une combinaison de méthodes communes map () et filter () :

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

L' extension takeUnless () est la même que takeIf () 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 ajoute un grand nombre de méthodes d'extension aux collections Java standard qui peuvent rendre notre code plus facile à utiliser .

Ces méthodes sont situées dans _Collections.kt , _Ranges.kt et _Sequences.kt , ainsi que dans _Arrays.kt pour que les méthodes équivalentes s'appliquent à la place aux tableaux . (N'oubliez pas que, dans Kotlin, les tableaux peuvent être traités de la même manière que les collections )

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

En plus des collections, Kotlin ajoute un nombre important de méthodes d'extension à la classe String - définie dans _Strings.kt . Ceux-ci nous permettent de traiter les chaînes comme s'il s'agissait de collections de caractères .

Toutes ces méthodes d'extension fonctionnent ensemble pour nous permettre d'écrire du 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 faire si nous avons besoin d'étendre une classe avec une nouvelle fonctionnalité - soit à partir de la bibliothèque standard Java ou Kotlin ou d'une bibliothèque dépendante que nous utilisons?

Les méthodes d'extension sont écrites comme n'importe quelle autre méthode , mais la classe réceptrice est fournie comme partie du nom de la fonction, séparée par le point.

Par exemple:

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

This will define a new function called escapeForXml as an extension to the String class, allowing us to call it as described above.

Inside this function, we can access the receiver using this, the same as if we had written this inside the String class itself:

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

3.1. Writing Generic Extension Methods

What if we want to write an extension method that is meant to be applied to multiple types, generically? We could just extend the Any type, – which is the equivalent of the Object class in Java – but there is a better way.

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() }

This could be applied to any type that meets the generic requirements, and inside the function this value is typesafe.

For example, using the above example:

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

3.2. Writing Infix Extension Methods

Infix methods are useful for writing DSL-style code, as they allow for methods to be called without the period or brackets:

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

We can now call this the same as any other infix method:

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

3.3. Writing Operator Extension Methods

We could also write an operator method as an extension.

Operator methods are ones that allow us to take advantage of the operator shorthand instead of the full method name – e.g., the plus operator method might be called using the + operator:

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

Again, this works the same as any other operator method:

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

4. Calling Kotlin Extension Function from Java

Let's now see how Java operates with Kotlin extension functions.

In general, every extension method we define in Kotlin is available for us to use in Java. We should remember, though, that the infix method still needs to be called with dot and parentheses. Same with operator extensions — we can't use only the plus character (+). These facilities are only available in Kotlin.

However, we can't call some of the standard Kotlin library methods in Java, like let or apply, because they're marked with @InlineOnly.

4.1. Visibility of the Custom Extension Function in Java

Let's use one of the previously defined extension functions — String.escapeXml(). Our file containing the extension method is called StringUtil.kt.

Now, when we need to call an extension method from Java, we need to use a class name StringUtilKt. Note that we have to add the Kt suffix:

String xml = "hi"; String escapedXml = StringUtilKt.escapeForXml(xml); assertEquals("hi", escapedXml);

Please pay attention to the first escapeForXml parameter. This additional argument is an extension function receiver type. Kotlin with top-level extension function is a pure Java class with a static method. That's why it needs to somehow pass the original String.

And of course, just like in Java, we can use static import:

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

4.2. Calling a Built-in Kotlin Extension Method

Kotlin helps us write code easier and faster by providing many built-in extensions functions. For example, there's the String.capitalize() method, which can be called directly from Java:

String name = "john"; String capitalizedName = StringsKt.capitalize(name); assertEquals("John", capitalizedName);

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

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

4.3. Renaming the Generated Java Static Class

We already know that a Kotlin extension function is a static Java method. Let's rename a generated Java class with an annotation @file:JvmName(name: String).

This has to be added at the top of the file:

@file:JvmName("Strings") package com.baeldung.kotlin fun String.escapeForXml() : String { return this .replace("&", "&") .replace("<", "", ">") }

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

Strings.escapeForXml(xml);

De plus, nous pouvons toujours ajouter une importation statique:

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

5. Résumé

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 n'ont pas les 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és sur GitHub.