ПрограммированиеSenior Kotlin Developer

Как работает ключевое слово 'super' в Kotlin и чем оно отличается от его использования в Java? В каких случаях возникнут сложности с неоднозначностью при множественной реализации интерфейсов и как их решать?

Проходите собеседования с ИИ помощником Hintsage

Ответ

В Kotlin ключевое слово super используется для обращения к реализации метода или свойства суперкласса либо реализуемого интерфейса. В отличие от Java, Kotlin позволяет явно указать, через какой интерфейс или класс вызвать реализацию метода, что особенно важно при "конфликте" нескольких интерфейсов с одинаковыми методами.

Особенность: Если класс реализует несколько интерфейсов, в которых определён метод с одинаковым сигнатурой и есть реализация по умолчанию, то необходимо явно указать, какую реализацию использовать.

Пример:

interface A { fun foo() = println("A") } interface B { fun foo() = println("B") } class C : A, B { override fun foo() { super<A>.foo() // Явно вызываем A.foo super<B>.foo() // Явно вызываем B.foo } }

Это не поддерживается в Java напрямую (Java не разрешает интерфейсам иметь поля/реализацию методов до Java 8, и даже в Java 8 — механизмы другие).

Вопрос с подвохом

"Можно ли в Kotlin обратиться к реализации метода суперкласса через несколько уровней наследования?"

  • Частая ошибка: думают, что можно обратиться к реализации через промежуточный класс: super<Intermediate>.foo(), но это не так — обращаться можно только к непосредственному суперклассу или интерфейсу, который реализует данный метод напрямую.

Пример:

open class Base { open fun foo() = println("Base") } open class Mid : Base() { override fun foo() { println("Mid"); super.foo() } } class Child: Mid() { override fun foo() { super.foo() // вызовет Mid.foo() // super<Base>.foo() — ошибка компиляции } }

Примеры реальных ошибок из-за незнания тонкостей темы


История

В большом проекте интерфейсы Logger и Auditor оба определяли метод record(). При реализации класса, наследующего оба интерфейса, программист не переопределил явно record(), в итоге возникла ошибка компиляции "Class must override public open fun record()". Пришлось реализовать метод вручную и явно делегировать в соответствующий интерфейс.


История

При попытке вызывать реализованный метод суперкласса через несколько промежуточных слоёв (например, super<Base>.foo()), возникала ошибка компиляции. Разработчик не знал ограничения Kotlin и не мог понять, почему прямой вызов невозможен.


История

После обновления API один из интерфейсов добавил реализацию функции по умолчанию, совпадающую с другим интерфейсом. Старый код больше не компилировался из-за конфликта реализаций — пришлось явно разрешать конфликт, реализуя метод самому и выбирать, какие реализации суперинтерфейсов вызывать внутри себя.