Разберите последствия объявления переопределяющего метода с более узким модификатором доступа.
Метод-наследник не может уменьшать доступность переопределяемого метода. Если метод базового класса объявлен как public, protected или доступен в пределах пакета, переопределяющий метод должен иметь не более строгий уровень доступа; иначе программа не скомпилируется.
Это правило сохраняет возможность использовать объект-наследник там, где ожидается объект базового класса. Код, имеющий право вызвать метод через базовый тип, не должен внезапно потерять это право из-за фактического типа объекта.
Наследование в Java предназначено не только для повторного использования реализации, но и для формирования отношения подстановки: экземпляр подкласса может выступать вместо экземпляра базового класса.
Система доступа и динамический полиморфизм должны работать согласованно. Поэтому переопределение не может сделать ранее доступную операцию недоступной для кода, который взаимодействует с объектом через базовый тип.
Представим, что базовый класс предоставляет публичный метод, а подкласс пытается объявить метод с той же сигнатурой как protected или private. При вызове через ссылку базового типа компилятор по-прежнему видит публичный контракт, но фактическая реализация принадлежит подклассу.
Если бы такое сужение разрешалось, один и тот же полиморфный вызов имел бы разную допустимость в зависимости от фактического класса объекта. Это нарушило бы контракт базового типа и создало бы несогласованность между проверкой доступа и выбором реализации.
При переопределении Java проверяет не только совпадение сигнатуры, но и совместимость модификаторов доступа. Разрешено сохранить тот же уровень доступа или расширить его: например, protected можно переопределить как protected или public, но нельзя как метод с доступом по умолчанию или private.
Минимальный пример:
Такой код не компилируется: execute в SecureService пытается уменьшить доступность публичного метода. Исправление состоит в объявлении метода как public.
Аннотация @Override полезна тем, что заставляет компилятор подтвердить факт переопределения. Без неё ошибка с изменением сигнатуры или модификатора всё равно может привести к неожиданному поведению, особенно если метод на самом деле не переопределяет родительский.
Метод private не переопределяется: он не наследуется подклассом. Поэтому одноимённый private-метод в наследнике является отдельным методом и не подчиняется правилу расширения доступа для переопределения.
Для методов с доступом по умолчанию важна граница пакета. В том же пакете такой метод может быть переопределён с сохранением или расширением доступа; в другом пакете он не наследуется как доступный для переопределения метод, поэтому ситуация уже не является обычным переопределением.
В библиотеке есть базовый класс обработчика с публичным методом process, который вызывается инфраструктурой через ссылку на базовый тип. Разработчик конкретного обработчика объявляет process как protected, рассчитывая скрыть его от внешнего кода.
Варианты решения:
public — сохраняется полиморфный контракт, но метод остаётся доступным внешнему коду;final, а расширение перенести в отдельный защищённый метод — доступ к алгоритму контролируется базовым классом, но меняется дизайн и добавляется шаблонный метод.Обычно выбирают третий вариант, если библиотека должна гарантировать инварианты до и после обработки. Если же подкласс действительно должен полностью заменить поведение базового метода, выбирают первый вариант и сохраняют public в переопределении.
protected-метод базового класса на public в наследнике?Да. Расширение доступности разрешено и часто используется, когда подкласс должен предоставить более широкий API. При этом остальные ограничения переопределения сохраняются: метод не должен быть static вместо экземплярного, а final-метод переопределить нельзя.
@Override и случайно изменить параметры метода?Переопределения не будет: появится перегруженный или совершенно новый метод. Вызов через ссылку базового типа продолжит выбирать реализацию базового класса, поскольку динамический полиморфизм работает только для действительно переопределённых методов.
Нет, если речь идёт о настоящем переопределении. Правило проверяется на этапе компиляции независимо от того, какие вызовы фактически написаны в программе. Если нужна отдельная менее доступная операция, её следует назвать иначе или скрыть реализацию в другом private-методе, не выдавая её за переопределение.