Kotlin guide done. (20.10.26)

This commit is contained in:
2026-10-20 12:54:25 +03:00
parent da3ad9dd1a
commit 6853ada348
81 changed files with 30112 additions and 0 deletions
@@ -0,0 +1,341 @@
---
title: Переопределение
description: override и final, вызов super, конфликт реализаций и ковариантность результата
tags:
- kotlin
- oop
---
`override` обязательно. Без него член не переопределяет, а конфликтует с родительским.
```kotlin
open class Animal2 {
open fun sound() = "..."
}
class Cat : Animal2() {
override fun sound() = "Мяу"
// fun sound() = "Мяу" // ошибка: 'sound' hides member and needs 'override'
}
fun main() {
val a: Animal2 = Cat()
println(a.sound()) // Мяу — вызов по фактическому типу
}
```
Это закрывает два класса ошибок: опечатка в имени создала бы новый метод, а добавление метода в базовый класс случайно «переопределилось» бы существующим методом подкласса. В Kotlin и то и другое - ошибка компиляции.
## final override
Переопределённый член остаётся открытым для дальнейшего переопределения. Закрыть ветку - `final`:
```kotlin
open class A5 { open fun f() = "A" }
open class B5 : A5() { final override fun f() = "B" }
// class C5 : B5() { override fun f() = "C" } // ошибка: f закрыт
fun main() { println(B5().f()) } // B
```
## super
```kotlin
open class Logger3 {
open fun log(msg: String) = "[базовый] $msg"
}
class TimestampLogger : Logger3() {
override fun log(msg: String) = super.log("[время] $msg")
}
fun main() {
println(TimestampLogger().log("привет")) // [базовый] [время] привет
}
```
`super` работает и для свойств - через их геттеры:
```kotlin
open class Base20 {
open val items: List<Int> get() = listOf(1, 2)
}
class Derived20 : Base20() {
override val items: List<Int> get() = super.items + 3
}
fun main() { println(Derived20().items) } // [1, 2, 3]
```
Из вложенного класса к `super` внешнего обращаются через `super@Outer`:
```kotlin
open class Base21 {
open fun name() = "база"
}
class Outer4 : Base21() {
override fun name() = "внешний"
inner class Inner4 {
fun bothNames() = "${name()} / ${super@Outer4.name()}"
}
}
fun main() { println(Outer4().Inner4().bothNames()) } // внешний / база
```
## Конфликт реализаций
Если два супертипа дают реализацию одного метода, компилятор требует разрешить конфликт явно:
```kotlin
interface A6 { fun f() = "A" }
interface B6 { fun f() = "B" }
class C6 : A6, B6 {
override fun f() = super<A6>.f() + super<B6>.f() // указываем источник
}
fun main() { println(C6().f()) } // AB
```
Без `override` такой класс не скомпилируется - Kotlin не выбирает реализацию за вас.
То же с классом и интерфейсом:
```kotlin
open class Base22 { open fun f() = "класс" }
interface Mixin { fun f() = "интерфейс" }
class Impl4 : Base22(), Mixin {
override fun f() = "${super<Base22>.f()} + ${super<Mixin>.f()}"
}
fun main() { println(Impl4().f()) } // класс + интерфейс
```
Подробнее: [Интерфейсы](Интерфейсы.md)
## Что можно переопределять
| Член | Условие |
|---|---|
| метод | `open` или `abstract` |
| `val` | `open` или `abstract`; можно расширить до `var` |
| `var` | `open` или `abstract`; сузить до `val` нельзя |
| свойство в конструкторе | `override val` в параметре |
```kotlin
abstract class Base23 {
abstract val size: Int
abstract var label: String
abstract fun compute(): Int
}
class Impl5(override var label: String, private val items: List<Int>) : Base23() {
override val size get() = items.size
override fun compute() = items.sum()
}
fun main() {
val i = Impl5("данные", listOf(1, 2, 3))
println("${i.label} ${i.size} ${i.compute()}") // данные 3 6
}
```
## Ковариантность результата
Переопределение может сузить тип результата:
```kotlin
open class Animal3
class Dog2 : Animal3()
open class Shelter {
open fun adopt(): Animal3 = Animal3()
}
class DogShelter : Shelter() {
override fun adopt(): Dog2 = Dog2() // Dog2 — подтип Animal3
}
fun main() {
val s: Shelter = DogShelter()
println(s.adopt()::class.simpleName) // Dog2
}
```
Это безопасно: клиент ожидает `Animal3`, а получает `Dog2`, который им является.
Параметры сузить нельзя - это сломало бы контракт:
```kotlin
open class Base24 {
open fun process(x: Any) = "any: $x"
}
class Derived24 : Base24() {
override fun process(x: Any) = "переопределено: $x"
fun process(x: String) = "перегрузка: $x" // это перегрузка, не override
}
fun main() {
val d = Derived24()
println(d.process("текст")) // перегрузка: текст
println((d as Base24).process("текст")) // переопределено: текст
}
```
Вызов зависит от статического типа ссылки - классическая ловушка смешения перегрузки и переопределения.
## Nullability при переопределении
```kotlin
open class Base25 {
open fun find(id: Int): String? = null
}
class Derived25 : Base25() {
override fun find(id: Int): String = "найдено $id" // сузили String? до String
}
fun main() {
val b: Base25 = Derived25()
println(b.find(1)) // найдено 1
}
```
Результат можно сделать non-null - это сужение. А параметр, наоборот, можно расширить до nullable:
```kotlin
open class Base26 {
open fun handle(x: String) = "строка"
}
class Derived26 : Base26() {
override fun handle(x: String) = "подкласс: $x"
}
fun main() { println(Derived26().handle("a")) } // подкласс: a
```
## Переопределение equals, hashCode, toString
Члены `Any` открыты всегда:
```kotlin
class Point4(val x: Int, val y: Int) {
override fun equals(other: Any?) = other is Point4 && other.x == x && other.y == y
override fun hashCode() = 31 * x + y
override fun toString() = "($x, $y)"
}
fun main() {
println(Point4(1, 2)) // (1, 2)
println(Point4(1, 2) == Point4(1, 2)) // true
}
```
Подробнее: [equals, hashCode и toString](../Синтаксис%20и%20типы/Базовые%20типы%20ООП/equals,%20hashCode%20и%20toString.md)
## Переопределение и расширения
Функция-расширение **не** переопределяется - она разрешается статически, по типу ссылки:
```kotlin
open class Base27
class Derived27 : Base27()
fun Base27.describe() = "база"
fun Derived27.describe() = "подкласс"
fun main() {
val d: Base27 = Derived27()
println(d.describe()) // база — по типу переменной!
println(Derived27().describe()) // подкласс
}
```
Это принципиальное отличие от методов. Если нужен полиморфизм - нужен метод класса. Подробнее: [Функции-расширения](../Функциональный%20Kotlin/Функции-расширения.md)
Расширение не может скрыть метод с той же сигнатурой: метод всегда побеждает.
```kotlin
class Service13 {
fun run2() = "метод"
}
fun Service13.run2() = "расширение" // никогда не вызовется
fun main() { println(Service13().run2()) } // метод
```
> [!info] Отличия от Java
> | Java | Kotlin |
> |---|---|
> | `@Override` опциональна | `override` обязательно |
> | метод виртуален, `final` закрывает | нужен `open`, иначе финален |
> | переопределённый метод остаётся виртуальным | то же, закрывается `final override` |
> | `super.method()` | то же |
> | `A.super.method()` при конфликте | `super<A>.method()` |
> | поля скрываются | свойства переопределяются |
> | ковариантный результат (Java 5+) | то же |
>
> **Обязательный `override`** - главное практическое улучшение. В Java `@Override` необязательна, поэтому опечатка в имени тихо создаёт новый метод, который никогда не вызовется.
>
> **Конфликт реализаций интерфейсов** решается похоже, но синтаксис другой: `A.super.f()` в Java против `super<A>.f()` в Kotlin. Требование явно разрешать конфликт одинаково строгое в обоих языках.
>
> **Сокрытие полей** - проблема Java, которой в Kotlin нет. Поле подкласса с тем же именем скрывает поле базового класса, и какое из них увидит код, зависит от типа ссылки:
> ```java
> class Base { int value = 1; }
> class Derived extends Base { int value = 2; }
> Base b = new Derived();
> // b.value == 1, ((Derived) b).value == 2
> ```
> В Kotlin свойства - это методы доступа, поэтому `override` работает полиморфно.
>
> **Статические методы в Java не переопределяются, а скрываются.** В Kotlin статики нет; члены `companion object` тоже не переопределяются, но путаницы меньше, потому что компаньон не наследуется.
>
> Сравнить: [Переопределение в Java](../../Программирование%20на%20Java/ООП/Наследование/Переопределение.md), [Сокрытие](../../Программирование%20на%20Java/ООП/Наследование/Сокрытие.md), [Ad-hoc полиморфизм](../../Программирование%20на%20Java/ООП/Полиморфизм/Ad-hoc%20(перегрузка).md)
> [!warning] Подводные камни
> **Расширение не переопределяется** - выбор идёт по статическому типу. Это самая частая ошибка ожиданий:
> ```kotlin
> open class Base28
> class Derived28 : Base28()
>
> fun Base28.name() = "база"
> fun Derived28.name() = "подкласс"
>
> fun main() {
> val list: List<Base28> = listOf(Derived28())
> println(list[0].name()) // база — не "подкласс"
> }
> ```
>
> **Перегрузка вместо переопределения компилируется молча:**
> ```kotlin
> open class Base29 { open fun f(x: Any) = "any" }
> class Derived29 : Base29() {
> override fun f(x: Any) = "override"
> fun f(x: Int) = "overload"
> }
>
> fun main() {
> val d = Derived29()
> println(d.f(1)) // overload — точнее по типу
> println((d as Base29).f(1)) // override — по типу ссылки
> }
> ```
>
> **Вызов `open`-метода из конструктора попадает в подкласс до его инициализации.** Подробнее: [Инициализация](Инициализация.md)
>
> **`super` в лямбде внутри метода работает, а в объекте-выражении - относится к нему:**
> ```kotlin
> open class Base30 { open fun f() = "база" }
>
> class Derived30 : Base30() {
> override fun f() = "подкласс"
> fun test(): String {
> val fromLambda = listOf(1).map { super.f() }.first() // база
> return fromLambda
> }
> }
>
> fun main() { println(Derived30().test()) } // база
> ```
>
> **Переопределение `equals` без `hashCode` ломает хеш-коллекции.** Компилятор не предупреждает.
>
> **Сужение видимости при переопределении запрещено**, а расширение - разрешено и легко делается случайно: `public override` у `protected`-метода публикует внутреннюю деталь.
>
> **Делегат свойства не наследуется.** Переопределяя `val x by lazy { }`, подкласс должен задать своё хранилище - делегат базового класса не переиспользуется:
> ```kotlin
> open class Base31 {
> open val value: String by lazy { "база" }
> }
>
> class Derived31 : Base31() {
> override val value: String by lazy { "подкласс" } // свой делегат
> }
>
> fun main() { println(Derived31().value) } // подкласс
> ```
Дополнительно: [Полиморфизм](Полиморфизм.md)
Далее: [Абстрактные классы](Абстрактные%20классы.md)