Зачем использовать наследование в Java
Что проверяют
Понимаете ли вы, что наследование это не способ переиспользовать код, и знаете ли, почему композиция обычно лучше.
Как отвечать
Наследование решает две задачи: задаёт отношение «является» и включает полиморфизм. Ссылка на базовый тип работает с любым наследником, и вызывающий код не знает, с кем именно имеет дело.
Где оно оправдано:
- подлинное отношение «является», а не «содержит».
CircleэтоShape.Carне являетсяEngine, он его содержит; - шаблонный метод: базовый класс задаёт скелет алгоритма, наследники подставляют шаги. Так устроены
InputStreamи многие каркасы; - частичная реализация в абстрактном классе, когда у наследников общее состояние, а не только общий интерфейс.
Почему им злоупотреблять нельзя, и это главная часть ответа:
- наследование ломает инкапсуляцию: наследник зависит не от контракта родителя, а от того, как родитель написан внутри. Классический пример из Effective Java: наследуетесь от
HashSet, переопределяетеaddиaddAll, и счётчик удваивается, потому чтоaddAllвнутри зовётadd. Ваш код сломался от изменения, которого вы не видели; - хрупкий базовый класс: правка родителя может незаметно сломать всех наследников;
- иерархия жёсткая: класс наследует ровно один, и поменять это потом дорого;
- нарушение принципа подстановки, когда наследник сужает поведение родителя. Хрестоматийный пример это квадрат, унаследованный от прямоугольника.
Отсюда практическое правило: предпочитать композицию и интерфейсы, а наследовать только внутри иерархии, которую вы контролируете целиком. Класс, не рассчитанный на наследование, объявляют final, а с версии 17 есть sealed для явно перечисленного набора наследников.
Где спотыкаются
Называют наследование способом не писать код дважды.
Не могут объяснить проблему хрупкого базового класса.
Не знают принцип подстановки Барбары Лисков по сути, только по названию.
Строят иерархию в пять уровней вместо интерфейса и делегирования.