Найти тему
Изучаем Java

Java11: наследование

У классов есть возможность наследовать реализацию другого класса. Это значит, что если у нас есть одинаковый код, который должен быть в двух классах, то нам не нужно его копировать из класса в класс, а можно указать, что класс наследует реализацию предка.

Проилюстрируем случай с общим методом:

Что здесь происходит:

  • У нас есть базовый класс A с методом methodA
  • Нам по какой-то причине понадобился такой же метод в классе B. Мы не копипастим код, а используем ключевое слово extends, которым указываем, что B наследует реализацию A.
  • Дальше в Main мы пишем код, который использует класс B.
  • Создаем объект класса B
    B b = new B();
  • Вызываем у этого объекта метод methodA
Вместо наследует можно сказать расширяет функционал класса A.

Тоже самое справедливо для полей:

-2

Что здесь происходит:

  • Мы создаем базовый класс A с полем fieldA
  • Наследуем это поле в классе-потомке B
  • Дальше создаем экземпляр класса B и используем fieldA таким же образом, как будто мы его объявляли ранее в классе B.
Объект и экземпляр - синонимы. Иногда еще говорят инстанс, как калька с английского instance

Модификаторы доступа и наследование

Вернемся к модификаторам доступа.
Кроме видимости для внешнимх классов, они так же влияют на видимость полей и методов для потомков. Члены класса помеченные, как private не видны в коде потомка.
В связи с наследованием добавляется еще промежуточный модификатор
protected, когда член класса не виден для внешнего кода (так же как и private), но его можно использовать в коде потомка.

Пример с модификатором protected:

-3

Резюмируя, у нас получается 3 модификатора доступа:

  • public - видно во внешнем коде и потомках
  • protected - видно в потомках, но не видно во внешнем коде
  • private - не видно в потомках, не видно во внешнем коде

Для удобства сведем видимость предка в зависимости от модификатора в таблицу:

-4

Абстрактные классы

Если класс предок нам интересен только с точки зрения переиспользования кода, а объекты такого класса мы не планируем создавать, то можно воспользоваться техникой абстрактных классов.

К определению класса добавляем ключевое слово abstract. Тем самым мы запрещаем создание таких объектов.

Тогда наш первый пример будет выглядеть так:

-5

Композиция и наследование

Под композицией имеется ввиду, что мы не наследуем реализацию, а включаем нужный нам класс ввиде поля и для запроса функционала обращаемся к этому полю.

Перепишем наш пример используя композицию:

-6

либо чуть короче

-7
В последнем примере мы не используем конструктор, а инициализируем поле fieldA прямо во время объявления класса. На самом деле это абсолютно эквивалетный код. Конструктор без параметра генерируется компилятором и в конструкторе происходит инициализация поля fieldA.

На этих “синтетических” примерах код с наследованием выглядит выигрышнее, но, зачастую, бесконтрольное использование наследования даже с благой целью переиспользования кода, приводит к запутанному и тяжело поддерживаемому коду. При проектировании есть смысл еще раз все взвесить и, возможно, отдать предпочтение композиции, а не наследованию.

Когда наследование оправдано

Если ваш проект подразумевает библиотеку классов, у вас достаточно времени, чтобы сделать продуманную иерархию классов и при этом предполагается, что зависимости между классами будут мало меняться в будущем (что уже шаткое предположение), то почему бы и нет. На мой взгляд для наследования хороший кандидат GUI библитека (отображение графического пользовательского интерфейса) - окна, диалоги, кнопки.

Полезные ссылки: