Kotlin guide done. (20.10.26)
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user