2015-07-24 30 views
5

Sto cercando di capire come Java gestisce i casi di ambiguità che emergono quando una classe concreta eredita metodi (astratti o concreti) aventi lo stesso nome da diverse classi/interfacce.Quali sono le regole per gestire i metodi ereditati dall'omonimo?

Non sono stato in grado di trovare una regola generale, ecco perché ho deciso, una volta per tutte, di dedicare un po 'di tempo a ciò usando un approccio pratico.

ho considerato 8 casi differenti, combinando

  • metodi astratti
  • metodi
  • non astratte
  • classi astratte
  • interfacce

conseguente questo schema:

      +-------------------------+ 
          |  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 qui tutti i casi è stato attuato e ha commentato:

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

Fatto che, in ogni caso, anche se sono in grado di comprendere la maggior parte dei casi nel mio Ad esempio, non sono ancora riuscito a dedurre alcuna "regola generale".

Ad esempio, non capisco perché casi 2a e 3a funzionano in modo diverso, cioè perché un'implementazione in classe astratta viene accettato mentre un'implementazione in interfaccia non è.

La mia ultima domanda è: esiste davvero una "regola genarale"? C'è un modo per prevedere come si comporterà il compilatore senza dover memorizzare ogni singolo caso?

EDIT

Potrebbe essere banale, ma credo che possa essere utile a qualcun altro mettere giù le mie considerazioni.

Penso che si può riassumere tutto la questione a questa semplice procedura:

Dato un codice di esempio come questo

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{} 
  1. Considerate la vostra classe C composta da tutti i suoi metodi e quelli ereditati (lo chiamano C *)

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

  2. Convalida C * contro le interfacce implementate (ogni ambiguità di metodo proveniente dalle interfacce, inclusi i metodi predefiniti, deve essere risolta in C dando un'implementazione).

    3a.Se C * dà una sosta implementazione valida qui

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

    3b. In caso contrario, un'implementazione valida è necessaria in C

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

+0

Si prega di formattare il tuo post. Sto avendo problemi con il grafico basato su testo che hai fornito –

+1

Posso vedere tutto correttamente formattato, a cosa ti riferisci? –

+1

@VinceEmigh Se sei su un dispositivo mobile, a volte usano un carattere a larghezza variabile per i blocchi di codice. –

risposta

4

Gli stati JLS (§9.4.1.3 comma 7):

Quando un metodo di default con firme astratto e vengono ereditate, produciamo un errore.

Hanno poi andare a spiegare perché hanno scelto questo:

In questo caso, sarebbe possibile dare priorità a uno o l'altro - forse ci potrebbe supporre che il metodo predefinito fornisce un implementazione ragionevole anche per il metodo astratto. Ma questo è rischioso, poiché oltre al nome e alla firma coincidenti, , non abbiamo motivo di credere che il metodo predefinito si comporti in modo coerente con il contratto del metodo astratto - il metodo predefinito potrebbe non esistere nemmeno quando la subinterfaccia è stata originariamente sviluppata. In questa situazione è più sicuro chiedere all'utente di affermare attivamente che l'implementazione predefinita sia appropriata (tramite una dichiarazione di priorità).

1

La regola generale è che le definizioni di una classe hanno la precedenza rispetto ai metodi difensore in un'interfaccia. Se un metodo è dichiarato astratto in una classe, ciò annulla l'esistenza di un'implementazione predefinita nell'interfaccia.

I metodi Defender sono stati introdotti come misura temporanea per consentire la crescita delle API di interfaccia senza compromettere la compatibilità con le versioni precedenti. La semantica doveva essere tale che i metodi di difesa non intralcassero le regole linguistiche esistenti.

+0

Potresti fornire link a qualsiasi riferimento? Il fatto che le lezioni abbiano la precedenza è stata anche la mia risposta intuitiva, mi piacerebbe approfondire questo aspetto –