Как граница пакета влияет на статус метода с одинаковой сигнатурой в классе-наследнике?
Если метод предка имеет пакетную область видимости, то метод с такой же сигнатурой в наследнике переопределяет его только внутри того же пакета. В другом пакете метод наследника не переопределяет метод предка, а объявляет самостоятельный метод, потому что пакетный метод предка там не наследуется.
Пакетная область видимости была введена как средство инкапсуляции реализации внутри пакета. Она позволяет нескольким классам одного пакета сотрудничать через внутренние методы, не включая эти методы в доступный для внешнего кода API.
Наследование при этом не отменяет правила доступа. Класс из другого пакета может расширить публичный или защищённый API предка, но не получает возможность переопределять его пакетные детали реализации.
Ошибка возникает, когда разработчик видит одинаковую сигнатуру метода в предке и наследнике и автоматически считает метод переопределённым. В другом пакете это неверно: вызов пакетного метода из кода предка продолжит обращаться к реализации предка, а метод наследника не будет участвовать в динамическом полиморфизме.
Последствие особенно опасно для шаблонного метода: разработчик ожидает, что подкласс изменит отдельный этап алгоритма, но фактически базовый класс продолжает выполнять собственную реализацию. Аннотация @Override помогает обнаружить такую ошибку на этапе компиляции.
В одном пакете пакетный метод доступен наследнику, поэтому объявление метода с той же сигнатурой является переопределением. Вызов через экземпляр наследника может быть динамически направлен к реализации наследника.
В другом пакете пакетный метод предка не наследуется. Поэтому одноимённый метод наследника не связан с ним отношением переопределения, даже если его имя, параметры и возвращаемый тип совпадают.
В этом примере Child.step не переопределяет Parent.step: метод Parent.step пакетный, а классы находятся в разных пакетах. Поэтому вызов run() использует Parent.step, а не Child.step; аннотация @Override над Child.step вызвала бы ошибку компиляции.
Если метод предка должен быть точкой расширения для наследников из других пакетов, его обычно объявляют protected или public с учётом требуемого API. У protected меньше поверхность публичного API, но он всё равно предоставляет наследникам контракт, который нужно поддерживать.
Важно не путать область видимости с динамическим выбором реализации. Динамический полиморфизм работает только для методов, которые действительно находятся в отношении переопределения; одинаковая сигнатура сама по себе этого отношения не создаёт.
Библиотека содержит базовый обработчик с публичным методом запуска и пакетным методом подготовки. Команда клиента создала наследника в собственном пакете и объявила метод подготовки с той же сигнатурой, ожидая изменить поведение обработки.
Возможный вариант — оставить пакетную область видимости. Плюс: внутренний метод не становится частью API; минус: внешние наследники не смогут его переопределить.
Второй вариант — переместить пользовательский наследник в пакет библиотеки. Это технически позволит переопределение, но создаст хрупкую зависимость от внутренней структуры пакетов и усложнит сопровождение.
Выбранное решение — явно объявить точку расширения как protected и документировать её контракт. Это делает намерение доступным компилятору и наследникам, а результатом становится предсказуемый полиморфный вызов без зависимости от расположения классов по пакетам.
Нет. Должны выполняться правила переопределения: метод предка должен быть доступен для наследования, метод не должен быть private или static, а сигнатура должна соответствовать требованиям Java. Пакетный метод из другого пакета не наследуется, поэтому совпадение сигнатуры там недостаточно.
@Override в наследнике из другого пакета?Компилятор сообщит об ошибке, потому что метод не переопределяет доступный метод предка. Это полезная проверка намерения: если разработчик ожидал полиморфное переопределение, ошибка обнаружится сразу, а не после анализа поведения приложения.
protected или public, если метод предка пакетный и находится в другом пакете?Да, такой метод можно объявить, но он всё равно не станет переопределением пакетного метода предка, поскольку исходный метод не наследуется через границу пакета. Метод наследника будет самостоятельным; чтобы получить настоящее переопределение, базовый метод должен иметь доступность, допускающую наследование из другого пакета, например protected или public.