Extensiones de Kotlin

Kotlin puede extender las propiedades y métodos de una clase sin necesidad de herencia ni del patrón Decorator.

La extensión es un comportamiento estático y no afecta en absoluto al código de la clase extendida.


Funciones de extensión

Las funciones de extensión pueden añadir nuevos métodos a clases existentes sin modificar la clase original. La forma de definición de una función de extensión es:

fun receiverType.functionName(params){
    body
}
  • receiverType: indica el receptor de la función, es decir, el objeto que se extiende con la función.
  • functionName: el nombre de la función de extensión.
  • params: los parámetros de la función de extensión, pueden ser NULL.

El siguiente ejemplo extiende la clase User:

class User(var name:String)

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

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

El resultado de ejecución del ejemplo es:

用户名 Ejemplo

El siguiente código añade a MutableListuna función 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())
}

El resultado de ejecución del ejemplo es:

[3, 2, 1]

La palabra clave this hace referencia al objeto receptor (receiver object), es decir, la instancia del objeto especificada antes del punto al llamar a la función de extensión.


Las funciones de extensión se resuelven estáticamente

Las funciones de extensión se resuelven estáticamente y no son miembros virtuales del tipo receptor. Al llamar a una función de extensión, cuál función se invoca concretamente está determinado por la expresión del objeto que realiza la llamada, no por el tipo dinámico:

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

El resultado de ejecución del ejemplo es:

c

Si la función de extensión coincide con una función miembro, al usar dicha función se dará prioridad a la función miembro.

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

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

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

El resultado de ejecución del ejemplo es:

función miembro

Extender un objeto nulo

Dentro de una función de extensión, se puede usar this para determinar si el receptor es NULL. De esta manera, incluso si el receptor es NULL, se puede llamar a la función de extensión. Por ejemplo:

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

El resultado de ejecución del ejemplo es:

null

Propiedades de extensión

Además de funciones, Kotlin también admite la extensión de propiedades:

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

Las propiedades de extensión pueden definirse en una clase o en un archivo Kotlin, pero no dentro de una función. Las propiedades de inicialización no pueden inicializarse porque las propiedades no tienen campo de respaldo (backing field); solo pueden definirse mediante getters/setters proporcionados explícitamente.

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

Las propiedades de extensión solo pueden declararse como val.


Extensiones de objetos acompañantes

Si una clase define un objeto acompañante, también puedes definir funciones y propiedades de extensión para el objeto acompañante.

El objeto acompañante se invoca mediante la forma "NombreDeClase.". Las funciones de extensión declaradas para el objeto acompañante se invocan usando el calificador del nombre de la clase:

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

El resultado de ejecución del ejemplo es:

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

Alcance de las extensiones

Normalmente, las funciones o propiedades de extensión se definen en un paquete de nivel superior:

package foo.bar

fun Baz.goo() { …… } 

Para usar una extensión fuera del paquete donde se define, importa el nombre de la función de extensión mediante import:

package com.example.usage

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

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

Extensiones declaradas como miembros

Dentro de una clase puedes declarar extensiones para otra clase.

En esta extensión hay múltiples receptores implícitos. La instancia de la clase donde se define el método de extensión se denomina receptor de despacho, mientras que la instancia del tipo objetivo del método de extensión se denomina receptor de extensión.

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)

}

El resultado de ejecución del ejemplo es:

D bar
C baz

Dentro de la clase C se crea una extensión para la clase D. En este caso, C se denomina receptor de despacho y D es el receptor de extensión. En el ejemplo anterior se puede ver claramente que, dentro de la función de extensión, se pueden llamar a las funciones miembro del receptor de despacho.

Si se llama a una función que existe tanto en el receptor de despacho como en el receptor de extensión, tiene prioridad el receptor de extensión. Para referenciar a los miembros del receptor de despacho puedes usar la sintaxis this calificada.

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)

}

El resultado de ejecución del ejemplo es:

D bar
C bar

Las funciones de extensión definidas como miembros pueden declararse como open y pueden sobrescribirse en subclases. Es decir, en el proceso de despacho de este tipo de funciones de extensión, son virtuales respecto al receptor de despacho, pero siguen siendo estáticas respecto al receptor de extensión.

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" —— 扩展接收者静态解析

}

El resultado de ejecución del ejemplo es:

D.foo in C
D.foo in C1
D.foo in C
Otras extensiones