Расширения
Kotlin предоставляет возможность расширять класс или интерфейс новой функциональностью без наследования от класса или использования таких шаблонов проектирования, как Decorator. Это делается с помощью специальных объявлений, называемых расширениями.
Например, вы можете написать новые функции для класса или интерфейса из библиотеки стороннего разработчика, которую нельзя модифицировать. Такие функции можно вызывать обычным способом, как будто они являются методами исходного класса. Этот механизм называется расширяющей функцией. Также существуют расширяемые свойства, позволяющие определять новые свойства для существующих классов.
Расширяющие функции
Чтобы объявить расширяющую функцию, префикс её имени с типом получателя, который относится к типу, расширяемому. Следующее добавляет swap функцию к MutableList<Int>.
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
Ключевое слово this внутри расширяющей функции соответствует объекту получателя (тому, что передается перед точкой). Теперь вы можете вызывать такую функцию для любого MutableList<Int>:
val list = mutableListOf(1, 2, 3) list.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'list'
Эта функция имеет смысл для любого MutableList<T>, и вы можете сделать её обобщённой:
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
}
Вам необходимо объявить параметр обобщённого типа перед именем функции, чтобы сделать его доступным в выражении типа получателя. Для получения дополнительной информации об обобщениях см. обобщённые функции.
Расширения решаются статически
Расширения фактически не изменяют классы, которые они расширяют. Объявив расширение, вы не вставляете новые члены в класс, а только делаете новые функции вызываемыми с использованием нотации точки на переменных этого типа.
Расширяющие функции диспетчеризуются статически, что означает, что они не являются виртуальными по типу получателя. Расширяющая функция, которая вызывается, определяется типом выражения, на котором вызывается функция, а не типом результата от оценки этого выражения во время выполнения. Например:
fun main() {
//sampleStart
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
//sampleEnd
}
Этот пример выводит Shape, потому что вызываемая расширяющая функция зависит только от объявленного типа параметра s, который является классом Shape.
Если класс имеет член-функцию, и определена расширяющая функция, которая имеет тот же тип получателя, то же имя и применима к заданным аргументам, то член всегда имеет приоритет. Например:
fun main() {
//sampleStart
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
//sampleEnd
}
Этот код выводит Метод класса.
Однако вполне допустимо, чтобы расширяющие функции перегружали члены-функции с тем же именем, но разной сигнатурой:
fun main() {
//sampleStart
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function #$i") }
Example().printFunctionType(1)
//sampleEnd
}
Получатель со значением null
Обратите внимание, что расширения могут быть определены с типом получателя со значением null. Эти расширения могут вызываться на переменной объекта, даже если её значение null, и они могут проверять на this == null внутри тела.
Таким образом, вы можете вызвать toString() в Kotlin без проверки на null, так как проверка выполняется внутри расширяющей функции:
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
Расширяемые свойства
Kotlin поддерживает расширяемые свойства так же, как и функции:
val <T> List<T>.lastIndex: Int
get() = size - 1
Пример:
val House.number = 1 // error: initializers are not allowed for extension properties
Расширения для объекта-компаньона
Если класс имеет определённый объект-компаньон, вы также можете определить расширяющие функции и свойства для объекта-компаньона. Как и обычные члены объекта-компаньона, их можно вызывать, используя только имя класса в качестве квалификатора:
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
Область действия расширений
В большинстве случаев вы определяете расширения на верхнем уровне, непосредственно под пакетами:
package org.example.declarations
fun List<String>.getLongestString() { /*...*/}
Чтобы использовать расширение вне объявляющего пакета, импортируйте его в месте вызова:
package org.example.usage
import org.example.declarations.getLongestString
fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}
См. Импорты для получения дополнительной информации.
Объявление расширений как членов
Вы можете объявить расширения для одного класса внутри другого класса. Внутри такого расширения есть несколько неявных получателей - объекты, чьи члены могут быть доступны без квалификатора. Экземпляр класса, в котором объявлено расширение, называется получателем диспетчеризации, а экземпляр типа получателя расширяющего метода называется расширяемым получателем.
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
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
//Host("kotl.in").printConnectionString() // error, the extension function is unavailable outside Connection
}
В случае конфликта имён между членами получателя диспетчеризации и расширяемого получателя, расширяемый получатель имеет приоритет. Для ссылки на член получателя диспетчеризации вы можете использовать синтаксис квалифицированного this.
class Connection {
fun Host.getConnectionString() {
toString() // calls Host.toString()
this@Connection.toString() // calls Connection.toString()
}
}
Расширения, объявленные как члены, могут быть объявлены как open и переопределены в подклассах. Это означает, что диспетчеризация таких функций является виртуальной по отношению к типу получателя диспетчеризации, но статической по отношению к типу расширяемого получателя.
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
}
Примечание по видимоcти
Расширения используют те же модификаторы видимости, что и обычные функции, объявленные в той же области. Например:
Расширение, объявленное на верхнем уровне файла, имеет доступ к другим
privateобъявлениям на верхнем уровне в том же файле.Если расширение объявлено вне типа получателя, оно не может получить доступ к
privateилиprotectedчленам получателя.
© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/extensions.html