Spec-Zone.ru › Kotlin 1.7

Расширения

Kotlin предоставляет возможность расширять класс или интерфейс новой функциональностью без необходимости наследования от класса или использования таких шаблонов проектирования, как Декоратор. Это делается с помощью специальных объявлений, называемых расширениями.

Например, вы можете написать новые функции для класса или интерфейса из сторонней библиотеки, которую нельзя модифицировать. Такие функции можно вызывать обычным способом, как если бы они были методами исходного класса. Этот механизм называется расширяющей функцией. Также существуют расширяемые свойства, которые позволяют определять новые свойства для существующих классов.

Расширяющие функции

Для объявления расширяющей функции, префикс её имени с типом получателя, который относится к типу, который расширяется. Следующее добавляет 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
}

Примечание о видимости

Расширения используют те же модификаторы видимости, что и обычные функции, объявленные в той же области. Например:

  • Расширение, объявленное на верхнем уровне файла, имеет доступ к другим private объявлениям на верхнем уровне в том же файле.

  • Если расширение объявлено вне типа получателя, оно не может получить доступ к private или protected членам получателя.

Последнее изменение: 02 июня 2022
Модификаторы видимости Классы данных

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/extensions.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API