Почему в Java наследование реализовано именно так? Всем доброго времени суток,
Недавно писал проект на Java. При этом несколько удивило поведение методов родительских классов.
Цитата из Вики:Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании метод (функция) класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения. Таким образом, программисту необязательно знать точный тип объекта для работы с ним через виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором метод объявлен. Одним из переводов слова virtual с английского языка может быть «фактический», что больше подходит по смыслу.
Виртуальные методы — один из важнейших приёмов реализации полиморфизма. Они позволяют создавать общий код, который может работать как с объектами базового класса, так и с объектами любого его класса-наследника. При этом базовый класс определяет способ работы с объектами и любые его наследники могут предоставлять конкретную реализацию этого способа.
Теперь конкретный случай.
Есть класс A с полем type типа String, есть класс B, наследующий A.
Предполагается, что A и все его классы наследники должны иметь метод getType(), выводящий строку типа (в общем-то, функционал одинаковый, и даже переопределять его не требуется).
Что имеем в итоге:
1. Попытка сделать такpublic class A {
...
public String getType() {
return this.type;
}
private String type = "null";
}
public class B {
...
private String type = "type_B";
}
приводит к тому, что всегда будет выводиться null. В общем-то, можно было предположить такое развитие событий, поскольку поле - private, и такое поле недоступно для объектов классов-потомков. Хотя формально такие поля, вроде бы, наследуются (?). IDE во всяком случае пишет, что "field has private access in class A", а не "field does not exist".
2. Попытка сделать поле protected в A и в B, оставив всё остальное как есть - приводит к предупреждению в IDE о том, что поле скрывает поле родительского класса, но результат исполнения совершенно не меняется.
3. Рабочие способы (две штуки):
- Полностью скопировать реализацию метода getType() в B, добавив Override аннотацию. Неплохо, позволяет даже оставить оба поля private. Но это дублирование совершенно одинакового кода, к тому же, это просто элементарно можно забыть сделать по невнимательности.
- Создать в B конструктор, назначающий значение полю type, убрать объявление поля type из B, в A сделать его protected. Работает, неплохо выглядит, разве что - в моём случае пришлось ради этого делать конструктор там, где он был не нужен, так как классы очень простые.
Вопрос:
Почему родительская реализация работает с полями класса-предка? В случае private поля это ещё относительно понятно, но в случае protected поля - нет. Ведь даже в момент вызова объект лежал в переменной, имеющей тип B. При этом вызывается реализация из A, это нормально, если в B я не переопределил её другой, но проблема в том, что и значения полей берутся из A! Почему так реализовано? В JS, например, с его цепочкой прототипов, результат был бы совсем другим.
P.S. Можно в коде реализации метода из A использовать Reflections, в принципе, чтобы получить фактическое значение поля объекта, но это уже совсем изврат.
P.P.S. Вариант с конструктором плох ещё и тем, что даже если в B предполагается свой конструктор - можно забыть там назначить значения нужным полям, опять же в силу невнимательности. Программисту интуитивно логичнее объявлять поля класса вместе с их дефолтными значениями внизу, единым блоком. Иногда это даже первое, что делается, когда создаётся класс-наследник. До момента написания конструктора про это банально можно забыть, и никто нам не напомнит, что у нас, оказывается, критичные protected поля не переназначены :)

21 Авг 2019 в 07:00
162 +1
0
Ответы
1

Для понимания такого поведения в Java необходимо разобраться в концепции наследования и доступа к полям в классах-наследниках.

В Java уровни доступа к полям (private, protected, public) определяют видимость этих полей внутри класса и его наследников. Поля с модификатором private видны только внутри самого класса, поля с модификатором protected видны внутри самого класса, его наследников и классов в том же пакете, а поля с модификатором public видны везде.

Когда вы объявляете поле с модификатором protected в классе A, оно становится доступным для его наследников. Таким образом, в классе B, который наследует класс A, вы можете обращаться к полю type из класса A, но не переопределяете это поле.

Когда вы вызываете метод getType() из объекта типа B, Java будет обращаться к методу getType() из класса A, так как метод не был переопределен в классе B. Из-за этого возвращается значение поля type из класса A, которое является значением по умолчанию "null".

Чтобы решить эту проблему, вам нужно либо переопределить метод getType() в классе B и вернуть значение из поля type класса B, либо передавать значение поля type в конструкторе класса B.

Этот подход к наследованию и доступу к полям является частью общей философии Java, которая стремится к защите данных (data encapsulation) и обеспечивает контролируемый доступ к полям класса через методы. Такой подход позволяет избежать ошибок и несанкционированного доступа к данным.

20 Апр в 13:09
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Название заказа не должно быть пустым
Введите email
Бесплатные доработки
Гарантированные бесплатные доработки
Быстрое выполнение
Быстрое выполнение от 2 часов
Проверка работы
Проверка работы на плагиат
Интересные статьи из справочника
Поможем написать учебную работу
Название заказа не должно быть пустым
Введите email
Доверьте свою работу экспертам
Разместите заказ
Наша система отправит ваш заказ на оценку 95 047 авторам
Первые отклики появятся уже в течение 10 минут
Прямой эфир