Kotlin
Méthodes d'extension
Recherche…
Syntaxe
- fun TypeName.extensionName (params, ...) {/ * body * /} // Déclaration
- fun <T: Any> TypeNameWithGenerics <T> .extensionName (params, ...) {/ * body * /} // Déclaration avec des génériques
- myObj.extensionName (args, ...) // invocation
Remarques
Les extensions sont résolues de manière statique . Cela signifie que la méthode d’extension à utiliser est déterminée par le type de référence de la variable à laquelle vous accédez; Peu importe le type de la variable à l'exécution, la même méthode d'extension sera toujours appelée. En effet, la déclaration d’une méthode d’extension n’ajoute pas réellement un membre au type de récepteur .
Extensions de niveau supérieur
Les méthodes d'extension de niveau supérieur ne sont pas contenues dans une classe.
fun IntArray.addTo(dest: IntArray) {
for (i in 0 .. size - 1) {
dest[i] += this[i]
}
}
Au-dessus d'une méthode d'extension est défini pour le type IntArray
. Notez que l'objet pour lequel la méthode d'extension est définie (appelé le récepteur ) est accessible à l'aide du mot this
clé this
.
Cette extension peut être appelée comme ceci:
val myArray = intArrayOf(1, 2, 3)
intArrayOf(4, 5, 6).addTo(myArray)
Piège potentiel: les extensions sont résolues de manière statique
La méthode d'extension à appeler est déterminée au moment de la compilation en fonction du type de référence de la variable à laquelle on accède. Quel que soit le type de la variable à l'exécution, la même méthode d'extension sera toujours appelée.
open class Super
class Sub : Super()
fun Super.myExtension() = "Defined for Super"
fun Sub.myExtension() = "Defined for Sub"
fun callMyExtension(myVar: Super) {
println(myVar.myExtension())
}
callMyExtension(Sub())
L'exemple ci-dessus affichera "Defined for Super"
, car le type déclaré de la variable myVar
est Super
.
Echantillon s'étendant longtemps pour rendre une chaîne lisible par l'homme
Étant donné toute valeur de type Int
ou Long
pour rendre une chaîne lisible par un humain:
fun Long.humanReadable(): String {
if (this <= 0) return "0"
val units = arrayOf("B", "KB", "MB", "GB", "TB", "EB")
val digitGroups = (Math.log10(this.toDouble())/Math.log10(1024.0)).toInt();
return DecimalFormat("#,##0.#").format(this/Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups];
}
fun Int.humanReadable(): String {
return this.toLong().humanReadable()
}
Puis facilement utilisé comme:
println(1999549L.humanReadable())
println(someInt.humanReadable())
Exemple d'extension de la classe Java 7+ Path
Un cas d'utilisation courant des méthodes d'extension consiste à améliorer une API existante. Voici des exemples d'ajout de exist
, notExists
et deleteRecursively
à la classe Java 7+ Path
:
fun Path.exists(): Boolean = Files.exists(this)
fun Path.notExists(): Boolean = !this.exists()
fun Path.deleteRecursively(): Boolean = this.toFile().deleteRecursively()
Qui peut maintenant être invoqué dans cet exemple:
val dir = Paths.get(dirName)
if (dir.exists()) dir.deleteRecursively()
Utilisation des fonctions d'extension pour améliorer la lisibilité
Dans Kotlin, vous pouvez écrire du code comme:
val x: Path = Paths.get("dirName").apply {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Mais l'utilisation de apply
n'est pas si claire quant à votre intention. Parfois, il est plus clair de créer une fonction d'extension similaire pour renommer l'action et la rendre plus évidente. Cela ne devrait pas être autorisé à devenir incontrôlable, mais pour des actions très courantes telles que la vérification:
infix inline fun <T> T.verifiedBy(verifyWith: (T) -> Unit): T {
verifyWith(this)
return this
}
infix inline fun <T: Any> T.verifiedWith(verifyWith: T.() -> Unit): T {
this.verifyWith()
return this
}
Vous pouvez maintenant écrire le code en tant que:
val x: Path = Paths.get("dirName") verifiedWith {
if (Files.notExists(this)) throw IllegalStateException("The important file does not exist")
}
Ce qui permet maintenant aux gens de savoir à quoi s’attendre dans le paramètre lambda.
Notez que le paramètre de type T
pour verifiedBy
est le même que T: Any?
ce qui signifie que même les types nullables pourront utiliser cette version de l'extension. Bien que verifiedWith
nécessite non-nullable.
Exemple d'extension de classes temporelles Java 8 pour le rendu d'une chaîne au format ISO
Avec cette déclaration:
fun Temporal.toIsoString(): String = DateTimeFormatter.ISO_INSTANT.format(this)
Vous pouvez maintenant simplement:
val dateAsString = someInstant.toIsoString()
Fonctions d'extension aux objets compagnons (apparition de fonctions statiques)
Si vous souhaitez étendre une classe, si vous êtes une fonction statique, par exemple pour la classe Something
fonction statique ajouter à la recherche fromString
, cela ne peut fonctionner que si la classe a un objet compagnon et que la fonction d'extension a été déclarée sur l'objet compagnon :
class Something {
companion object {}
}
class SomethingElse {
}
fun Something.Companion.fromString(s: String): Something = ...
fun SomethingElse.fromString(s: String): SomethingElse = ...
fun main(args: Array<String>) {
Something.fromString("") //valid as extension function declared upon the
//companion object
SomethingElse().fromString("") //valid, function invoked on instance not
//statically
SomethingElse.fromString("") //invalid
}
Solution de contournement de propriété d'extension paresseuse
Supposons que vous souhaitiez créer une propriété d'extension coûteuse à calculer. Vous souhaitez donc mettre le calcul en cache en utilisant le délégué de propriété paresseux et faire référence à l'instance actuelle ( this
), mais vous ne pouvez pas le faire, comme expliqué dans les problèmes Kotlin KT-9686 et KT-13053 . Cependant, il existe une solution de contournement officielle fournie ici .
Dans l'exemple, la propriété d'extension est color
. Il utilise un colorCache
explicite qui peut être utilisé avec this
car aucun lazy
n'est nécessaire:
class KColor(val value: Int)
private val colorCache = mutableMapOf<KColor, Color>()
val KColor.color: Color
get() = colorCache.getOrPut(this) { Color(value, true) }
Extensions pour une référence plus facile Voir du code
Vous pouvez utiliser des extensions pour la vue de référence, plus de passe-partout après avoir créé les vues.
Idée originale de la bibliothèque Anko
Les extensions
inline fun <reified T : View> View.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Activity.find(id: Int): T = findViewById(id) as T
inline fun <reified T : View> Fragment.find(id: Int): T = view?.findViewById(id) as T
inline fun <reified T : View> RecyclerView.ViewHolder.find(id: Int): T = itemView?.findViewById(id) as T
inline fun <reified T : View> View.findOptional(id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Activity.findOptional(id: Int): T? = findViewById(id) as? T
inline fun <reified T : View> Fragment.findOptional(id: Int): T? = view?.findViewById(id) as? T
inline fun <reified T : View> RecyclerView.ViewHolder.findOptional(id: Int): T? = itemView?.findViewById(id) as? T
Usage
val yourButton by lazy { find<Button>(R.id.yourButtonId) }
val yourText by lazy { find<TextView>(R.id.yourTextId) }
val yourEdittextOptional by lazy { findOptional<EditText>(R.id.yourOptionEdittextId) }