Inheritance
All classes in Kotlin have a common superclass, Any , which is the default superclass for a class with no supertypes declared:
Any has three methods: equals() , hashCode() , and toString() . Thus, these methods are defined for all Kotlin classes.
By default, Kotlin classes are final – they can’t be inherited. To make a class inheritable, mark it with the open keyword:
To declare an explicit supertype, place the type after a colon in the class header:
If the derived class has a primary constructor, the base class can (and must) be initialized in that primary constructor according to its parameters.
If the derived class has no primary constructor, then each secondary constructor has to initialize the base type using the super keyword or it has to delegate to another constructor which does. Note that in this case different secondary constructors can call different constructors of the base type:
Overriding methods
Kotlin requires explicit modifiers for overridable members and overrides:
The override modifier is required for Circle.draw() . If it’s missing, the compiler will complain. If there is no open modifier on a function, like Shape.fill() , declaring a method with the same signature in a subclass is not allowed, either with override or without it. The open modifier has no effect when added to members of a final class – a class without an open modifier.
A member marked override is itself open, so it may be overridden in subclasses. If you want to prohibit re-overriding, use final :
Overriding properties
The overriding mechanism works on properties in the same way that it does on methods. Properties declared on a superclass that are then redeclared on a derived class must be prefaced with override , and they must have a compatible type. Each declared property can be overridden by a property with an initializer or by a property with a get method:
You can also override a val property with a var property, but not vice versa. This is allowed because a val property essentially declares a get method, and overriding it as a var additionally declares a set method in the derived class.
Note that you can use the override keyword as part of the property declaration in a primary constructor:
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 >
Derived class initialization order
During the construction of a new instance of a derived class, the base class initialization is done as the first step (preceded only by evaluation of the arguments for the base class constructor), which means that it happens before the initialization logic of the derived class is run.
This means that when the base class constructor is executed, the properties declared or overridden in the derived class have not yet been initialized. Using any of those properties in the base class initialization logic (either directly or indirectly through another overridden open member implementation) may lead to incorrect behavior or a runtime failure. When designing a base class, you should therefore avoid using open members in the constructors, property initializers, or init blocks.
Calling the superclass implementation
Code in a derived class can call its superclass functions and property accessor implementations using the super keyword:
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 >
Inside an inner class, accessing the superclass of the outer class is done using the super keyword qualified with the outer class name: super@Outer :
Overriding rules
In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits multiple implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones).
To denote the supertype from which the inherited implementation is taken, use super qualified by the supertype name in angle brackets, such as super :
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
It’s fine to inherit from both Rectangle and Polygon , but both of them have their implementations of draw() , so you need to override draw() in Square and provide a separate implementation for it to eliminate the ambiguity.
Как в Kotlin работает множественное наследование (multiple inheritance)?
В Kotlin множественное наследование запрещено из соображений безопасности и снижения сложности языка. Вместо этого Kotlin использует механизм интерфейсов, который позволяет имитировать множественное наследование через реализацию нескольких интерфейсов одновременно.
Множественное наследование может быть опасно, так как может возникнуть неоднозначность при наследовании методов с одинаковыми именами из разных базовых классов. В Kotlin это решается через запрет на множественное наследование и использование интерфейсов для определения контрактов, которые должен реализовать класс.
К примеру, рассмотрим следующий код:
interface A < fun foo() < print("A") >> interface B < fun foo() < print("B") >> class C : A, B < override fun foo() < super.foo() super.foo() > >
В классе C мы реализуем два интерфейса A и B и перегружаем метод foo(), который выводит на консоль букву «A» и «B». Ключевое слово super<> позволяет явно указать, какой родительский класс используется для вызова метода foo(). Такой код позволяет избежать конфликта имен методов при множественном наследовании, которое не поддерживается в Kotlin.
В результате, если мы создадим экземпляр класса C и вызовем его метод foo() следующим образом:
то на консоль выведется «AB», т.е. метод foo() вызывается из обоих интерфейсов. Таким образом, можно использовать интерфейсы для имитации множественного наследования в Kotlin.