Spec-Zone.ru › Kotlin 1.7

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

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

Последнее изменение: 06 сентября 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