Какое поле прочитает Java при обращении к скрытому полю через ссылку базового типа?
Java выбирает поле по статическому типу выражения, через которое выполняется обращение, а не по фактическому типу объекта. Поля не переопределяются: одноимённое поле в классе-наследнике скрывает поле базового класса, поэтому ссылка базового типа обращается к полю базового класса.
В объектной модели Java динамический полиморфизм предназначен прежде всего для поведения, то есть для вызова экземплярных методов. Состояние объекта представлено полями, доступ к которым разрешается статически, что делает обращение к ним предсказуемым во время компиляции и не создаёт отдельного механизма динамического выбора полей.
Такой подход также предотвращает неоднозначность: объект-наследник может содержать два независимых поля с одинаковым именем — одно унаследованное и одно объявленное в наследнике.
Если класс-наследник объявляет поле с именем поля базового класса, разработчик может ошибочно ожидать поведения, аналогичного переопределению метода. Это особенно опасно при передаче объекта через ссылку базового типа: чтение и запись могут обращаться к полю базового класса, хотя фактический объект относится к классу-наследнику.
В результате состояние может рассинхронизироваться. Один участок программы изменит поле наследника, а другой прочитает поле базового класса и получит другое значение.
При обращении к полю компилятор сначала определяет статический тип выражения и разрешает имя поля в этом типе. Во время выполнения тип объекта не участвует в выборе поля. Если поле базового класса и поле наследника доступны и имеют одинаковое имя, это два разных хранилища состояния.
В первом обращении статический тип ref — Base, поэтому выбирается Base.value. После приведения статический тип выражения становится Child, и выбирается Child.value; сам объект при этом остаётся тем же.
Это отличается от экземплярных методов: для переопределённого метода реализация обычно выбирается по фактическому типу объекта. Поле нельзя сделать полиморфным через @Override, поскольку аннотация применима к методам и некоторым другим элементам, но не к полям.
Модификатор final не меняет правило разрешения имени: он запрещает повторное присваивание конкретному полю, но не превращает поле в переопределяемый член. Для static-полей действует тот же принцип статического выбора, дополнительно такие поля принадлежат классу, а не экземпляру.
Практически одноимённые поля в иерархии лучше не использовать для представления одного логического свойства. Если нужен полиморфный доступ к состоянию, обычно применяют методы доступа или абстрактные операции, которые могут быть переопределены.
В базовом классе модели находилось поле status, а в наследнике появилось одноимённое поле с новым набором допустимых значений. Часть кода работала с объектами через тип базового класса и читала старое поле, тогда как методы наследника изменяли новое. Это привело к противоречивым данным при сериализации и проверке состояния.
Рассматривались два варианта. Переименование поля в наследнике было быстрым, но сохраняло публичный прямой доступ к состоянию и не устраняло риск подобных ошибок в будущем. Сохранение двух полей с документированным различием почти не требовало изменений, но оставляло сложную и хрупкую модель данных.
Выбрали закрытые поля и методы доступа с единым источником состояния. В результате логическое свойство стало обслуживаться полиморфными методами, а компилятор и ревью перестали допускать неявное обращение к разным одноимённым хранилищам.
Меняет ли приведение ссылки тип выбранного поля?
Да, приведение меняет статический тип конкретного выражения, поэтому может изменить выбранное поле. Оно не преобразует объект и не объединяет поля: у объекта по-прежнему могут существовать отдельные Base.value и Child.value. Приведение безопасно только тогда, когда фактический объект действительно совместим с целевым типом; иначе возникнет ClassCastException.
Что произойдёт, если одноимённое поле базового класса объявлено как private?
Такое поле недоступно напрямую из класса-наследника и не считается унаследованным для целей доступа наследника. Поле с тем же именем в наследнике всё равно будет отдельным полем, но внешнее обращение к нему через тип базового класса возможно только через доступный метод базового класса либо не будет возможно вовсе. Закрытость поля не превращает его в полиморфное.
Можно ли использовать поля полиморфно через интерфейсную ссылку?
Интерфейс не предоставляет экземплярную переменную, которую класс реализует с динамическим разрешением. Поля, объявленные в интерфейсе, являются public static final константами и разрешаются по типу интерфейсного имени или выражения, а не по фактическому классу объекта. Для полиморфного поведения интерфейс должен объявлять методы, а не рассчитывать на одноимённые поля в реализациях.