Spec-Zone.ru › Kotlin 1.9

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

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

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