Extensions en Kotlin

Kotlin peut étendre les propriétés et les méthodes d'une classe sans avoir besoin de l'héritage ni du patron Decorator.

L'extension est un comportement statique et n'affecte pas le code de la classe étendue elle-même.


Fonctions d'extension

Les fonctions d'extension peuvent ajouter de nouvelles méthodes à des classes existantes sans modifier la classe d'origine. La forme de définition d'une fonction d'extension est :

fun receiverType.functionName(params){
    body
}
  • receiverType : indique le récepteur de la fonction, c'est-à-dire l'objet étendu par la fonction
  • functionName : le nom de la fonction d'extension
  • params : paramètres de la fonction d'extension, peut être NULL

L'exemple suivant étend la classe User :

class User(var name:String)

/**扩展函数**/
fun User.Print(){
    print("用户名 $name")
}

fun main(arg:Array<String>){
    var user = User("Example")
    user.Print()
}

La sortie d'exécution de l'exemple est :

用户名 Example

Le code suivant concerne MutableListpour ajouter une fonction swap :

// 扩展函数 swap,调换不同位置的值
fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1]     //  this 对应该列表
    this[index1] = this[index2]
    this[index2] = tmp
}

fun main(args: Array<String>) {

    val l = mutableListOf(1, 2, 3)
    // 位置 0 和 2 的值做了互换
    l.swap(0, 2) // 'swap()' 函数内的 'this' 将指向 'l' 的值

    println(l.toString())
}

La sortie d'exécution de l'exemple est :

[3, 2, 1]

Le mot-clé this fait référence à l’objet récepteur (c’est-à-dire l’instance d’objet spécifiée avant le point lors de l’appel d’une fonction d’extension).


Les fonctions d’extension sont résolues statiquement

Les fonctions d’extension sont résolues statiquement ; elles ne sont pas des membres virtuels du type récepteur. Lors de l’appel d’une fonction d’extension, la fonction qui est appelée est déterminée par l’expression d’objet qui appelle la fonction, et non par le type dynamique :

open class C

class D: C()

fun C.foo() = "c"   // 扩展函数 foo

fun D.foo() = "d"   // 扩展函数 foo

fun printFoo(c: C) {
    println(c.foo())  // 类型是 C 类
}

fun main(arg:Array<String>){
    printFoo(D())
}

La sortie d'exécution de l'exemple est :

c

Si une fonction d’extension et une fonction membre sont identiques, la fonction membre est utilisée en priorité lors de l’utilisation de cette fonction.

class C {
    fun foo() { println("成员函数") }
}

fun C.foo() { println("扩展函数") }

fun main(arg:Array<String>){
    var c = C()
    c.foo()
}

La sortie d'exécution de l'exemple est :

Member function

Étendre un objet null

À l’intérieur d’une fonction d’extension, vous pouvez utiliser this pour déterminer si le récepteur est NULL. De cette façon, même si le récepteur est NULL, la fonction d’extension peut toujours être appelée. Par exemple :

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}
fun main(arg:Array<String>){
    var t = null
    println(t.toString())
}

La sortie d'exécution de l'exemple est :

null

Propriétés d’extension

Outre les fonctions, Kotlin prend également en charge l’extension de propriétés à l’aide de propriétés :

val <T> List<T>.lastIndex: Int
    get() = size - 1
 

Les propriétés d’extension peuvent être définies dans des classes ou des fichiers Kotlin, mais pas dans des fonctions. Comme les propriétés d’extension n’ont pas de champ de stockage, elles ne peuvent pas être initialisées ; elles ne peuvent être définies que par des getters/setters fournis explicitement.

val Foo.bar = 1 // 错误:扩展属性不能有初始化器

Les propriétés d’extension ne peuvent être déclarées qu’avec val.


Extensions pour les objets compagnons

Si une classe définit un objet compagnon, vous pouvez également définir des fonctions d’extension et des propriétés pour l’objet compagnon.

Un objet compagnon est appelé sous la forme « Class. ». Les fonctions d'extension déclarées pour un objet compagnon sont appelées à l'aide du qualificateur de nom de classe :

class MyClass {
    companion object { }  // 将被称为 "Companion"
}

fun MyClass.Companion.foo() {
    println("伴随对象的扩展函数")
}

val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}

La sortie d'exécution de l'exemple est :

no:10
伴随对象的扩展函数

Portée des extensions

Généralement, les fonctions ou propriétés d'extension sont définies dans un package de niveau supérieur :

package foo.bar

fun Baz.goo() { …… } 

Pour utiliser une extension en dehors du package dans lequel elle est définie, importez le nom de la fonction d'extension via import :

package com.example.usage

import foo.bar.goo // 导入所有名为 goo 的扩展
                   // 或者
import foo.bar.*   // 从 foo.bar 导入一切

fun usage(baz: Baz) {
    baz.goo()
}

Extensions déclarées en tant que membres

À l'intérieur d'une classe, vous pouvez déclarer des extensions pour une autre classe.

Dans une telle extension, il existe plusieurs récepteurs implicites. L'instance de la classe où la méthode d'extension est définie est appelée récepteur de dispatch, et l'instance du type cible de la méthode d'extension est appelée récepteur d'extension.

class D {
    fun bar() { println("D bar") }
}

class C {
    fun baz() { println("C baz") }

    fun D.foo() {
        bar()   // 调用 D.bar
        baz()   // 调用 C.baz
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}

La sortie d'exécution de l'exemple est :

D bar
C baz

À l'intérieur de la classe C, une extension pour la classe D est créée. À ce stade, on appelle C le récepteur de dispatch, et D le récepteur d'extension. D'après l'exemple ci-dessus, on voit clairement que dans une fonction d'extension, vous pouvez appeler les fonctions membres du récepteur de dispatch.

Si une fonction est appelée et qu'elle existe à la fois dans le récepteur de dispatch et dans le récepteur d'extension, le récepteur d'extension est prioritaire. Pour référencer des membres du récepteur de dispatch, vous pouvez utiliser la syntaxe this qualifié.

class D {
    fun bar() { println("D bar") }
}

class C {
    fun bar() { println("C bar") }  // 与 D 类 的 bar 同名

    fun D.foo() {
        bar()         // 调用 D.bar(),扩展接收者优先
        [email protected]()  // 调用 C.bar()
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}

La sortie d'exécution de l'exemple est :

D bar
C bar

Les fonctions d'extension définies comme membres peuvent être déclarées open et peuvent être redéfinies dans les sous-classes. Autrement dit, dans le processus de dispatch de telles fonctions d'extension, elles sont virtuelles pour le récepteur de dispatch, mais restent statiques pour le récepteur d'extension.

open class D {
}

class D1 : D() {
}

open class C {
    open fun D.foo() {
        println("D.foo in C")
    }

    open fun D1.foo() {
        println("D1.foo in C")
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in C1")
    }
}


fun main(args: Array<String>) {
    C().caller(D())   // 输出 "D.foo in C"
    C1().caller(D())  // 输出 "D.foo in C1" —— 分发接收者虚拟解析
    C().caller(D1())  // 输出 "D.foo in C" —— 扩展接收者静态解析

}

La sortie d'exécution de l'exemple est :

D.foo in C
D.foo in C1
D.foo in C
Autres extensions