Quais são as regras para lidar com métodos herdados de homônimos?

Estou tentando entender como o Java lida com casos de ambiguidade que surgem quando uma classe concreta herda métodos (abstratos ou concretos) com o mesmo nome de diferentes classes / interfaces.

Não consegui encontrar uma regra geral, por isso decidi, de uma vez por todas, dedicar algum tempo a isso usando uma abordagem prática.

Eu considerei 8 casos diferentes, combinando

métodos abstratosmétodos não abstratosaulas abstratasinterfaces

resultando neste esquema:

                           +-------------------------+
                           |       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      |
+-----------+--------------+----------+--------------+

E aqui todos os casos são implementados e comentados:

// (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(){}     }

Feito isso, de qualquer maneira, embora eu seja capaz de entender a maioria dos casos no meu exemplo, ainda não consegui inferir nenhuma "regra geral".

Por exemplo, não entendo por que os casos2a e3a funcionam de maneira diferente, ou seja, por que uma implementação fornecida pela classe abstrata é aceita, enquanto uma implementação fornecida pela interface não é.

Minha pergunta final é: existe alguma "regra genaral" realmente? Existe alguma maneira de prever como o compilador se comportará sem precisar memorizar todos os casos?

EDITAR

Pode ser trivial, mas acho que pode ser útil para outra pessoa que rejeita minhas considerações.

Eu acho que você pode resumir toda a pergunta para estas etapas simples:

Dado um exemplo de código como este

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{}

Considere sua classe C composta por todos os seus métodos e métodos herdados (chame de C *)

class C* implements I,J{protected void foo(){};}

Valide C * com relação às interfaces implementadas (toda ambiguidade de método proveniente de interfaces, incluindo métodos padrão, deve ser resolvida em C, fornecendo uma implementação).

3a. Se C * der uma implementação válida, pare aqui

(it's not the case because method visibility cannot be reduced from public to protected)

3b Caso contrário, é necessária uma implementação válida em C

class C extends B implements I,J{public void foo(){}}

questionAnswers(2)

yourAnswerToTheQuestion