Почему в 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 поля не переназначены :)
Для понимания такого поведения в 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) и обеспечивает контролируемый доступ к полям класса через методы. Такой подход позволяет избежать ошибок и несанкционированного доступа к данным.
Для понимания такого поведения в 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) и обеспечивает контролируемый доступ к полям класса через методы. Такой подход позволяет избежать ошибок и несанкционированного доступа к данным.