Spec-Zone.ru › Kotlin 1.6

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

Все классы в 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 не оказывает никакого влияния, когда добавляется к членам final-класса – класса без модификатора 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 и предоставить отдельную реализацию для устранения неоднозначности.

Последнее изменение: 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/inheritance.html

Spec-Zone.ru

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