Spec-Zone.ru › Kotlin 1.6

Расширения

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
}

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

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

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

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

Последнее изменение: 07 апреля 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