ПрограммированиеAndroid разработчик

Опишите различия между абстрактными классами и интерфейсами в Kotlin. Когда какой подход применять, какие особенности реализации есть в языке и каковы нюансы наследования?

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

Ответ.

В Kotlin абстрактные классы (abstract class) и интерфейсы (interface) используются для объявления абстрактной логики, но между ними есть существенные различия:

  • Абстрактный класс может содержать состояния (поля), реализации методов и абстрактные методы. Класс может наследовать только один абстрактный (или обычный) класс. Конструкторы могут быть у абстрактного класса.
  • Интерфейс может содержать объявления свойств (без хранения состояния!), дефолтные реализации методов и абстрактные методы. С Kotlin 1.1 интерфейс может содержать реализации свойств, но они не могут иметь состояния. Класс может реализовать много интерфейсов.
  • Интерфейсы не могут содержать состояния (backing field), тогда как абстрактные классы — могут.

Пример кода

abstract class Animal(val name: String) { abstract fun makeSound() fun info() = println("I am $name") } interface Movable { fun move() } class Cat(name: String) : Animal(name), Movable { override fun makeSound() = println("Meow") override fun move() = println("Cat moves") }

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

Может ли интерфейс в Kotlin содержать состояния (backing field)?

Большинство отвечает, что можно объявить свойства в интерфейсе, но они все равно не имеют backing field — только геттеры/сеттеры без хранения значения. Даже если написать get() = ... — значение будет вычисляться каждый раз.

Пример:

interface MyInterface { val value: Int get() = 42 // нет хранения значения, вычисление на лету }

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


История

На крупном проекте старший разработчик пытался хранить состояние в интерфейсе (например, счетчик), ожидая, что свойство var count: Int обзаведется backing field. В результате у всех реализующих интерфейс классов приходилось реализовывать хранение значения по-разному, что вылилось в нестабильную логику (данные терялись, если забывали переопределить setter).


История

Один из разработчиков использовал абстрактный класс там, где требовалась множественная реализация различных стратегий поведения (паттерн стратегия). В результате возникла проблема: лишь один базовый класс на иерархию, но для разных аспектов поведения требовалась композиция. Пришлось менять архитектуру на интерфейсы.


История

Команда пыталась наследовать сразу два абстрактных класса (один с логикой, другой с общими утилитами), что невозможно в Kotlin. Проблема выявилась уже на стадии расширения приложения. Это вылилось в значительный технический долг, поскольку классы пришлось срочно реорганизовывать.