Каковы правила обработки унаследованных омонимами методов?
Я пытаюсь понять, как Java обрабатывает случаи неоднозначности, которые возникают, когда конкретный класс наследует (абстрактный или конкретный) методы, имеющие одно и то же имя, от разных классов / интерфейсов.
Мне не удалось найти общее правило, поэтому я решил раз и навсегда потратить некоторое время на это, используя практический подход.
Я рассмотрел 8 разных случаев, объединяя
абстрактные методынеабстрактные методыабстрактные классыинтерфейсыВ результате получается следующая схема:
+-------------------------+
| INTERFACE |
+----------+--------------|
| abstract | non-abstract |
| method | method |
+-----------+--------------+----------+--------------+
| | abstract | | |
| ABSTRACT | method | 1a | 2a |
| +--------------+----------+--------------+
| CLASS | non-abstract | | |
| | method | 3a | 4a |
+-----------+--------------+----------+--------------+
| | abstract | | |
| | method | 1b | 2b |
| INTERFACE +--------------+----------+--------------+
| | non-abstract | | |
| | method | 3b | 4b |
+-----------+--------------+----------+--------------+
И здесь каждый случай реализован и прокомментирован:
// (1a)
// A - abstract method
// I - abstract method
//
// Implementation needed to avoid compilation error:
// "The type B1 must implement the inherited abstract method A1.foo()"
//
abstract class A1{ abstract void foo(); }
interface I1{ void foo(); }
class B1 extends A1 implements I1{ public void foo(){} }
// (2a)
// A - abstract method
// I - non-abstract method
//
// Implementation needed to avoid compilation error:
// "The type B2 must implement the inherited abstract method A2.foo()"
//
abstract class A2{ abstract void foo(); }
interface I2{ default void foo(){} }
class B2 extends A2 implements I2{ public void foo(){} }
// (3a)
// A - non-abstract method
// I - abstract method
//
// Implementation not needed
//
abstract class A3{ public void foo(){} }
interface I3{ void foo(); }
class B3 extends A3 implements I3{ }
// (4a)
// A - non-abstract method
// I - non-abstract method
//
// Implementation not needed
//
abstract class A4 { public void foo(){System.out.println("A4");}}
interface I4{default void foo(){ System.out.println("I4");} }
class B4 extends A4 implements I4{ B4(){foo();} /*prints "A4"*/ }
// (1b)
// J - abstract method
// K - abstract method
//
// Implementation needed to avoid compilation error:
// "The type C1 must implement the inherited abstract method K1.foo()"
//
interface J1{ void foo(); }
interface K1{ void foo(); }
class C1 implements J1,K1{ public void foo(){} }
// (2b)
// J - abstract method
// K - non-abstract method
//
// Implementation needed to avoid compilation error:
// "The default method foo() inherited from K2 conflicts with another
// method inherited from J2"
//
interface J2{ void foo(); }
interface K2{ default void foo(){} }
class C2 implements J2,K2{ public void foo(){} }
// (3b)
// J - non-abstract method
// K - abstract method
//
// Implementation needed to avoid compilation error:
// "The default method foo() inherited from J3 conflicts with another
// method inherited from K3"
//
interface J3{ default void foo(){} }
interface K3{ void foo(); }
class C3 implements J3,K3{ public void foo(){} }
// (4b)
// J - non-abstract method
// K - non-abstract method
//
// Implementation needed to avoid compilation error:
// "Duplicate default methods named foo with the parameters () and ()
// are inherited from the types K4 and J4"
//
interface J4{ default void foo(){} }
interface K4{ default void foo(){} }
class C4 implements J4,K4{ public void foo(){} }
Сделано это, во всяком случае, хотя я могу понять большинство случаев в моем примере, я еще не смог вывести какое-либо «общее правило».
Например, я не понимаю, почему дела2а а также3a работают по-другому, то есть почему реализация, заданная абстрактным классом, принимается, а реализация, предоставляемая интерфейсом, - нет.
Мой последний вопрос: существует ли какое-либо «общее правило»? Есть ли способ, которым я могу предсказать, как будет вести себя компилятор без необходимости запоминать каждый случай?
РЕДАКТИРОВАТЬЭто может быть тривиально, но я думаю, что это может быть полезно, если кто-то еще излагает мои соображения.
Я думаю, что вы можете обобщить все вопросы к этим простым шагам:
Приведенный пример кода, как это
abstract class A{abstract void foo();}
abstract class B extends A {protected void foo(){}}
interface I{void foo();}
interface J{default void foo(){}}
class C extends B implements I,J{}
Рассмотрим ваш класс C, состоящий из всех его методов и унаследованных (назовите его C *)
class C* implements I,J{protected void foo(){};}
Проверяйте C * по отношению к интерфейсам, которые он реализует (каждая неоднозначность метода, исходящая от интерфейсов, включая методы по умолчанию, должна быть разрешена в C путем предоставления реализации).
3a. Если C * дает действительную реализацию, остановитесь здесь
(it's not the case because method visibility cannot be reduced from public to protected)
3b. В противном случае требуется правильная реализация в C
class C extends B implements I,J{public void foo(){}}