Spec-Zone.ru › Kotlin 1.8

Наследование

Все классы в Kotlin имеют общий суперкласс, Any, который является по умолчанию суперклассом для класса без объявленных супертипов:

class Example // Implicitly inherits from Any

Any имеет три метода: equals(), hashCode(), и toString(). Таким образом, эти методы определены для всех классов Kotlin.

По умолчанию классы Kotlin являются final – их нельзя унаследовать. Чтобы сделать класс наследуемым, пометьте его ключевым словом open:

open class Base // Class is open for inheritance

Чтобы объявить явный супертип, поместите тип после двоеточия в заголовке класса:

open class Base(p: Int)

class Derived(p: Int) : Base(p)

Если у производного класса есть первичный конструктор, базовый класс можно (и нужно) инициализировать в этом первичном конструкторе согласно его параметрам.

Если у производного класса нет первичного конструктора, то каждый вторичный конструктор должен инициализировать базовый тип, используя ключевое слово super, или должен делегировать другому конструктору, который это делает. Обратите внимание, что в этом случае разные вторичные конструкторы могут вызывать разные конструкторы базового типа:

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

Переопределение методов

Kotlin требует явных модификаторов для переопределяемых членов и переопределений:

open class Shape {
    open fun draw() { /*...*/ }
    fun fill() { /*...*/ }
}

class Circle() : Shape() {
    override fun draw() { /*...*/ }
}

Модификатор override требуется для Circle.draw(). Если его нет, компилятор выдаст ошибку. Если на функции нет модификатора open, например, Shape.fill(), объявление метода с тем же сигнатурой в подклассе не допускается, ни с override, ни без него. Модификатор open не оказывает никакого влияния, когда добавляется к членам конечного класса — класса без модификатора open.

Член, помеченный override, сам по себе открыт, поэтому его можно переопределить в подклассах. Если вы хотите запретить повторное переопределение, используйте final:

open class Rectangle() : Shape() {
    final override fun draw() { /*...*/ }
}

Переопределение свойств

Механизм переопределения работает со свойствами так же, как и с методами. Свойства, объявленные в суперклассе и затем повторно объявленные в производном классе, должны предваряться override, и они должны иметь совместимый тип. Каждое объявленное свойство может быть переопределено свойством с инициализатором или свойством с методом get:

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override val vertexCount = 4
}

Вы также можете переопределить val свойство с var свойством, но не наоборот. Это разрешено, потому что val свойство по сути объявляет метод get, а переопределение его как var дополнительно объявляет метод set в производном классе.

Обратите внимание, что вы можете использовать ключевое слово override в качестве части объявления свойства в первичном конструкторе:

interface Shape {
    val vertexCount: Int
}

class Rectangle(override val vertexCount: Int = 4) : Shape // Always has 4 vertices

class Polygon : Shape {
    override var vertexCount: Int = 0  // Can be set to any number later
}

Порядок инициализации производного класса

При создании нового экземпляра производного класса, инициализация базового класса выполняется как первый шаг (предшествующий только оценке аргументов для конструктора базового класса), что означает, что это происходит до выполнения логики инициализации производного класса.

//sampleStart
open class Base(val name: String) {

    init { println("Initializing a base class") }

    open val size: Int = 
        name.length.also { println("Initializing size in the base class: $it") }
}

class Derived(
    name: String,
    val lastName: String,
) : Base(name.replaceFirstChar { it.uppercase() }.also { println("Argument for the base class: $it") }) {

    init { println("Initializing a derived class") }

    override val size: Int =
        (super.size + lastName.length).also { println("Initializing size in the derived class: $it") }
}
//sampleEnd

fun main() {
    println("Constructing the derived class(\"hello\", \"world\")")
    Derived("hello", "world")
}

Это означает, что когда выполняется конструктор базового класса, свойства, объявленные или переопределенные в производном классе, еще не были инициализированы. Использование каких-либо из этих свойств в логике инициализации базового класса (либо напрямую, либо косвенно через другое переопределенное open член реализации) может привести к неправильному поведению или сбоям во время выполнения. При проектировании базового класса следует избегать использования open членов в конструкторах, инициализаторах свойств или init блоках.

Вызов реализации суперкласса

Код в производном классе может вызывать функции и реализации аксессоров свойств своего суперкласса, используя ключевое слово super:

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

class FilledRectangle : Rectangle() {
    override fun draw() {
        super.draw()
        println("Filling the rectangle")
    }

    val fillColor: String get() = super.borderColor
}

Внутри внутреннего класса доступ к суперклассу внешнего класса осуществляется с помощью ключевого слова super с квалификацией имени внешнего класса: super@Outer:

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

//sampleStart
class FilledRectangle: Rectangle() {
    override fun draw() {
        val filler = Filler()
        filler.drawAndFill()
    }
    
    inner class Filler {
        fun fill() { println("Filling") }
        fun drawAndFill() {
            super@FilledRectangle.draw() // Calls Rectangle's implementation of draw()
            fill()
            println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // Uses Rectangle's implementation of borderColor's get()
        }
    }
}
//sampleEnd

fun main() {
    val fr = FilledRectangle()
        fr.draw()
}

Правила переопределения

В Kotlin наследование реализации регулируется следующим правилом: если класс наследует несколько реализаций одного и того же члена от своих непосредственных суперклассов, он должен переопределить этот член и предоставить собственную реализацию (возможно, используя одну из унаследованных).

Чтобы указать супертип, из которого взята унаследованная реализация, используйте super с квалификацией имени супертипа в угловых скобках, например super<Base>:

open class Rectangle {
    open fun draw() { /* ... */ }
}

interface Polygon {
    fun draw() { /* ... */ } // interface members are 'open' by default
}

class Square() : Rectangle(), Polygon {
    // The compiler requires draw() to be overridden:
    override fun draw() {
        super<Rectangle>.draw() // call to Rectangle.draw()
        super<Polygon>.draw() // call to Polygon.draw()
    }
}

В порядке вещей наследование от Rectangle и Polygon, но оба из них имеют свои реализации draw(), поэтому необходимо переопределить draw() в Square и предоставить отдельную реализацию для устранения неоднозначности.

Последнее изменение: 10 января 2023
Классы Свойства

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

Spec-Zone.ru

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