В следующем коде объясните, почему вызов A.super.m() не компилируется, хотя B наследует метод от A:
interface A {
default void m() {
System.out.println("A");
}
}
interface B extends A {
}
class C implements B {
void call() {
A.super.m();
}
}
A.super.m() запрещён, потому что A не является непосредственным суперинтерфейсом класса C; непосредственный суперинтерфейс — только B. Для вызова унаследованной реализации нужно написать B.super.m().
Квалифицированный вызов Интерфейс.super.метод() разрешён только для прямого суперинтерфейса текущего класса. В данном случае B.super.m() фактически вызовет реализацию m, унаследованную B от A.
Механизм default-методов появился в Java 8, чтобы интерфейсы могли получать новые методы с реализацией без немедленной поломки всех существующих классов-реализаторов. Вместе с этим потребовался явный способ обратиться к реализации интерфейса, если класс хочет использовать её напрямую, а не обычный виртуальный вызов.
Синтаксис Интерфейс.super.метод() решает эту задачу для непосредственного суперинтерфейса. Ограничение защищает иерархию интерфейсов: подкласс не должен произвольно обходить более специфичный уровень и вызывать реализацию далёкого предка.
B является подинтерфейсом A, поэтому метод m доступен классу C через B. Однако доступность метода и возможность квалифицированного вызова — разные правила.
Если разрешить A.super.m() в таком коде, C смог бы явно обойти возможную переопределённую default-реализацию в B. Это создало бы неоднозначную семантику: класс объявляет реализацию через B, но принудительно выбирает более старый уровень иерархии.
В выражении Интерфейс.super.m() имя интерфейса обозначает не любой предок, а непосредственный суперинтерфейс текущего класса. У C список непосредственных суперинтерфейсов содержит B, поэтому допустим следующий вызов:
Поскольку B не объявляет собственную реализацию, его default-метод наследуется от A, и B.super.m() приводит к вызову этой реализации. Если B переопределит m, тот же вызов начнёт использовать реализацию B.
Это не обычный полиморфный вызов через объект. Вызов через B.super явно выбирает реализацию, связанную с интерфейсом B, поэтому переопределение m в классе C не будет вызвано:
Ограничение действует на этапе компиляции. Если интерфейс не является непосредственным суперинтерфейсом, компилятор отклоняет квалификатор A.super, даже если метод m фактически унаследован через него.
Предположим, библиотечный интерфейс BaseLogger содержит default-метод log, а прикладной интерфейс ServiceLogger расширяет его и позже добавляет собственное форматирование. Класс сервиса реализует ServiceLogger и хочет явно выбрать базовую реализацию.
Вариант BaseLogger.super.log() не компилируется: класс непосредственно связан с ServiceLogger, а не с BaseLogger. Вариант ServiceLogger.super.log() корректен и автоматически использует наиболее специфичную default-реализацию, доступную через этот прямой интерфейс.
Можно также вызвать обычный log() или this.log(). Это поддерживает полиморфизм и учитывает переопределение метода в классе, но не гарантирует выбор именно default-реализации интерфейса. Поэтому для намеренного обхода реализации класса подходит ServiceLogger.super.log(), а для обычного поведения — виртуальный вызов log().
Такой выбор делает зависимость явной: Интерфейс.super следует применять только когда классу действительно нужна конкретная реализация интерфейса, а не полиморфное поведение.
Что произойдёт, если B переопределит m?
При вызове B.super.m() будет выбрана реализация B, а не A. Квалификатор указывает на непосредственный интерфейс B, и разрешение default-метода начинается с него. Обращение к A.super.m() по-прежнему останется недопустимым.
Вызывает ли B.super.m() переопределение m в классе C?
Нет. Это специальный вызов реализации суперинтерфейса, а не вызов через ссылку на текущий объект. Если написать обычный m(), будет применён динамический диспетчинг и вызван метод C, если он его переопределяет; B.super.m() этот механизм намеренно обходит.
Можно ли вызвать B.super.m(), если B не содержит default-метод напрямую?
Да, если m доступен в B как унаследованный default-метод. В исходном примере B не объявляет m, но наследует его от A, поэтому B.super.m() корректен и вызывает реализацию A. Если же B переобъявит m как абстрактный метод, конкретный класс должен предоставить реализацию, а вызов B.super.m() станет недопустимым, поскольку у B не будет доступной default-реализации.