Наследование
У всех классов в 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».
Чтобы объявить явный супертип, укажите его тип после двоеточия в заголовке класса:
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)
}
Ключевое слово Open
В Kotlin ключевое слово open указывает, что класс или его член (функцию или свойство) можно переопределить в подклассах. По умолчанию классы Kotlin и их члены являются final, то есть от них нельзя наследоваться (если речь о классах) или переопределять их (если речь о членах), если только вы явно не пометите их как open:
// Base class with the open keyword to allow inheritance
open class Person(
val name: String
) {
// Open function that can be overridden in a subclass
open fun introduce() {
println("Hello, my name is $name.")
}
}
// Subclass inheriting from Person and overriding the introduce() function
class Student(
name: String,
val school: String
) : Person(name) {
override fun introduce() {
println("Hi, I'm $name, and I study at $school.")
}
}
Если вы переопределяете член базового класса, переопределённый член также по умолчанию является открытым. Если вы хотите изменить это поведение и запретить подклассам вашего класса переопределять вашу реализацию, можно явно пометить переопределённый член как final:
// Base class with the open keyword to allow inheritance
open class Person(val name: String) {
// Open function that can be overridden in a subclass
open fun introduce() {
println("Hello, my name is $name.")
}
}
// A subclass that inherits from Person and overrides the introduce() function
class Student(name: String, val school: String) : Person(name) {
// The final keyword prevents further overrides in subclasses
final override fun introduce() {
println("Hi, I'm $name, and I study at $school.")
}
}
Переопределение методов
В 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 и предоставить для него отдельную реализацию, чтобы устранить неоднозначность.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/inheritance.html