1.1.50.11. fejezet, Kiterjesztések

A Kotlin lehetővé teszi egy osztály vagy interfész új funkciókkal való bővítését anélkül, hogy örökölnie kellene az osztálytól, vagy olyan tervezési mintákat kellene használnia, mint a Decorator. Ez speciális deklarációkkal, úgynevezett kiterjesztésekkel történik.

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}
 
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'list'

Ez a kód generikusok alkalmazásával általánosítható:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}
open class Shape
class Rectangle: Shape()
 
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
 
fun printClassName(s: Shape) {
    println(s.getName())
}
 
printClassName(Rectangle())

A válasz erre a kódra a Shape

Ha van ugyanolyan nevű és szignatúrájú metódus az osztályban, akkor mindig a metódus hívódik meg, nem íródik felül.

class Example {
    fun printFunctionType() { println("Class method") }
}
 
fun Example.printFunctionType() { println("Extension function") }
 
Example().printFunctionType()

Erre a kódra a válasz a "Class method".

Azonban ha ugyanolyan nevű de eltérő szignatúrájú metódust valósítunk meg, akkor az hívódik meg:

class Example {
    fun printFunctionType() { println("Class method") }
}
 
fun Example.printFunctionType(i: Int) { println("Extension function #$i") }
 
Example().printFunctionType(1)

Ennek a kódnak a kimenete: "Extension function #1".

Nullázható fogadó

fun Any?.toString(): String {
    if (this == null) return "null"
    // After the null check, 'this' is autocast to a non-nullable type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

Tulajdonság kiterjesztés

val <T> List<T>.lastIndex: Int
    get() = size - 1
 
val House.number = 1 // error: initializers are not allowed for extension properties

Kísérőobjektum-kiterjesztések

Ha egy osztályhoz társobjektum van definiálva, akkor kiterjesztési függvényeket és tulajdonságokat is definiálhat a társobjektumhoz.

class MyClass {
    companion object { }  // will be called "Companion"
}
 
fun MyClass.Companion.printCompanion() { println("companion") }
 
fun main() {
    MyClass.printCompanion()
}

A bővítmények hatásköre

Ha definiáljuk az egyik csomagban a getLongestString metódust:

package org.example.declarations
 
fun List<String>.getLongestString() { /*...*/}

akkor azt importálni kell az alkalmazó csomagban a következő képen:

package org.example.usage
 
import org.example.declarations.getLongestString
 
fun main() {
    val list = listOf("red", "green", "blue")
    list.getLongestString()
}

Bővítmény deklarálása tagként

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}
 
class Connection(val host: Host, val port: Int) {
    fun printPort() { print(port) }
 
    fun Host.printConnectionString() {
        printHostname()   // calls Host.printHostname()
        print(":")
        printPort()   // calls Connection.printPort()
    }
 
    fun connect() {
        /*...*/
        host.printConnectionString()   // calls the extension function
    }
}

Bővítményből meghívni a deklaráló osztály metódusát így lehet:

class Connection {
    fun Host.getConnectionString() {
        toString()         // calls Host.toString()
        this@Connection.toString()  // calls Connection.toString()
    }
}

Öröklődés esetén a következőket érdemes figyelembe venni a bővítményeknél:

open class Base { }
 
class Derived : Base() { }
 
open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }
 
    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }
 
    fun call(b: Base) {
        b.printFunctionInfo()   // call the extension function
    }
}
 
class DerivedCaller: BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }
 
    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}
 
fun main() {
    BaseCaller().call(Base())   // "Base extension function in BaseCaller"
    DerivedCaller().call(Base())  // "Base extension function in DerivedCaller" - dispatch receiver is resolved virtually
    DerivedCaller().call(Derived())  // "Base extension function in DerivedCaller" - extension receiver is resolved statically
}

Láthatóság

A bővítmények az osztályoknál, metódusoknál és tulajdonságoknál meghatározott láthatóságokkal rendelkeznek.