Implementazione di due interfacce in una classe con lo stesso metodo. Quale metodo di interfaccia viene ignorato?


235

Due interfacce con nomi e firme dello stesso metodo. Ma implementato da una singola classe, allora come il compilatore identificherà quale metodo è per quale interfaccia?

Ex:

interface A{
  int f();
}

interface B{
  int f();
}

class Test implements A, B{   
  public static void main(String... args) throws Exception{   

  }

  @Override
  public int f() {  // from which interface A or B
    return 0;
  }
}   

Risposte:


337

Se un tipo implementa due interfacce e ognuna interfacedefinisce un metodo con firma identica, in effetti esiste un solo metodo e non sono distinguibili. Se, per esempio, i due metodi hanno tipi di ritorno in conflitto, allora si tratterà di un errore di compilazione. Questa è la regola generale dell'ereditarietà, dell'override del metodo, del nascondimento e delle dichiarazioni, e si applica anche a possibili conflitti non solo tra 2 interfacemetodi ereditati , ma anche un interfacee un classmetodo super , o anche solo conflitti dovuti alla cancellazione del tipo di generici.


Esempio di compatibilità

Ecco un esempio in cui hai un interface Gift, che ha un present()metodo (come in, presentare regali), e anche un interface Guest, che ha anche un present()metodo (come in, l'ospite è presente e non assente).

Presentable johnnyè sia a Giftche a Guest.

public class InterfaceTest {
    interface Gift  { void present(); }
    interface Guest { void present(); }

    interface Presentable extends Gift, Guest { }

    public static void main(String[] args) {
        Presentable johnny = new Presentable() {
            @Override public void present() {
                System.out.println("Heeeereee's Johnny!!!");
            }
        };
        johnny.present();                     // "Heeeereee's Johnny!!!"

        ((Gift) johnny).present();            // "Heeeereee's Johnny!!!"
        ((Guest) johnny).present();           // "Heeeereee's Johnny!!!"

        Gift johnnyAsGift = (Gift) johnny;
        johnnyAsGift.present();               // "Heeeereee's Johnny!!!"

        Guest johnnyAsGuest = (Guest) johnny;
        johnnyAsGuest.present();              // "Heeeereee's Johnny!!!"
    }
}

Lo snippet sopra riportato viene compilato ed eseguito.

Si noti che ce n'è solo uno @Override necessario !!! . Questo perché Gift.present()e Guest.present()sono " @Overrideequivalenti" ( JLS 8.4.2 ).

Quindi, johnny ha solo un'implementazione di present(), e non importa come tratti johnny, sia come Giftche come Guest, c'è solo un metodo da invocare.


Esempio di incompatibilità

Ecco un esempio in cui i due metodi ereditati NON sono @Overrideequivalenti:

public class InterfaceTest {
    interface Gift  { void present(); }
    interface Guest { boolean present(); }

    interface Presentable extends Gift, Guest { } // DOES NOT COMPILE!!!
    // "types InterfaceTest.Guest and InterfaceTest.Gift are incompatible;
    //  both define present(), but with unrelated return types"
}

Ciò ribadisce inoltre che ereditare i membri da una interfacedeve obbedire alla regola generale delle dichiarazioni dei membri. Qui abbiamo Gifte Guestdefiniamo present()con tipi di ritorno incompatibili: uno voidl'altro boolean. Per lo stesso motivo per cui non è possibile un void present()e un boolean present()tipo, questo esempio genera un errore di compilazione.


Sommario

È possibile ereditare metodi @Overrideequivalenti, soggetti ai normali requisiti di sostituzione e occlusione del metodo. Dal momento che SONO @Override equivalenti, in effetti esiste solo un metodo da implementare, e quindi non c'è nulla da distinguere / selezionare.

Il compilatore non deve identificare quale metodo è per quale interfaccia, perché una volta che sono determinati per essere @Overrideequivalenti, sono lo stesso metodo.

Risolvere potenziali incompatibilità può essere un compito complicato, ma questo è un altro problema.

Riferimenti


Grazie - questo è stato utile. Tuttavia, ho avuto un'ulteriore domanda sull'incompatibilità, che ho pubblicato come nuova domanda
amaidment

2
A proposito, questo cambia un po 'con il supporto dei defaultmetodi in Java 8.
Peter Lawrey,

Classi composte per risolvere potenziali incompatibilità possono essere il trucco :), ma non ho mai avuto un tale problema, ed è comunque evidente che potrebbe accadere.
Aquarius Power il

1
Questo articolo presenta un modello di progettazione che può essere utilizzato per affrontare in qualche modo la situazione in cui è necessario implementare due interfacce di collisione, diciamo Fooe Bar. Fondamentalmente hai la tua classe implementare una delle interfacce, diciamo Foo, e fornire un Bar asBar()metodo per restituire una classe interna che implementa la seconda Barinterfaccia. Non perfetto poiché la tua classe non è in definitiva "un bar", ma potrebbe essere utile in alcune circostanze.
Javaru,

1
im uno sviluppatore Java, ma c # è davvero più intelligente su questo: stackoverflow.com/questions/2371178/...
Amir Ziarati

25

Questo è stato contrassegnato come duplicato di questa domanda /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

È necessario Java 8 per ottenere un problema di eredità multipla, ma non è ancora un problema diamon in quanto tale.

interface A {
    default void hi() { System.out.println("A"); }
}

interface B {
    default void hi() { System.out.println("B"); }
}

class AB implements A, B { // won't compile
}

new AB().hi(); // won't compile.

Come commenta JB Nizet, puoi risolvere il mio problema prioritario.

class AB implements A, B {
    public void hi() { A.super.hi(); }
}

Tuttavia, non hai problemi con

interface D extends A { }

interface E extends A { }

interface F extends A {
    default void hi() { System.out.println("F"); }
}

class DE implement D, E { }

new DE().hi(); // prints A

class DEF implement D, E, F { }

new DEF().hi(); // prints F as it is closer in the heirarchy than A.

Wow. Questo mi è nuovo. Perché dovevano creare le impostazioni predefinite in Java 8?
Erran Morad,

1
Per facilitare l'aggiunta di nuovi metodi alle interfacce (in particolare le interfacce delle raccolte) senza interrompere il 60% della base di codice.
Tassos Bassoukos,

@BoratSagdiyev Il motivo principale è stato quello di supportare le chiusure e renderle più utili. Vedi Collection.stream (). Dai un'occhiata a List.sort () docs.oracle.com/javase/8/docs/api/java/util/… Hanno aggiunto un metodo per tutti gli elenchi, senza dover modificare alcuna implementazione specifica. Hanno aggiunto Collection.removeIf () che è utile
Peter Lawrey,

@TassosBassoukos +1 dice che hai la tua implementazione di List, ora puoi myList.stream () o myList.sort () senza cambiare il tuo codice
Peter Lawrey,

3
@PeterLawrey: AB non verrà compilato perché deve eseguire l'override hi()(per correggere l'ambiguità). Ad esempio, implementandolo comeA.super.hi() per scegliere di implementarlo allo stesso modo di A.
JB Nizet,

20

Per quanto riguarda il compilatore, questi due metodi sono identici. Ci sarà un'implementazione di entrambi.

Questo non è un problema se i due metodi sono effettivamente identici, in quanto dovrebbero avere la stessa implementazione. Se sono contrattualmente differenti (come da documentazione per ciascuna interfaccia), avrai problemi.


2
Spiega perché Java non ti consente di estendere più di una classe
Arthur Ronald,

1
@ArthurRonald, in realtà sembra solo correlato. Tuttavia, IMO, la classe che estende più di una classe può imbattersi in Diamond Problem (che è lo stato oggetto duplicato nella classe più derivata) ed è molto probabilmente il motivo per cui Java ha allontanato i suoi utenti dai problemi. D'altra parte, la classe che implementa più di una classe non può mai imbattersi in Diamond Problem semplicemente perché l'interfaccia non fornisce lo stato agli oggetti. E il problema è dovuto esclusivamente alle limitazioni della sintassi - incapacità di qualificare completamente la chiamata di funzione.
uvsmtid

13

Non c'è niente da identificare. Le interfacce vietano solo il nome e la firma di un metodo. Se entrambe le interfacce hanno un metodo con esattamente lo stesso nome e firma, la classe di implementazione può implementare entrambi i metodi di interfaccia con un unico metodo concreto.

Tuttavia, se i contratti semantici del metodo delle due interfacce sono contraddittori, ti sei praticamente perso; non è quindi possibile implementare entrambe le interfacce in una singola classe.


4

Prova a implementare l'interfaccia come anonima.

public class MyClass extends MySuperClass implements MyInterface{

MyInterface myInterface = new MyInterface(){

/* Overrided method from interface */
@override
public void method1(){

}

};

/* Overrided method from superclass*/
@override
public void method1(){

}

}

4

Come nell'interfaccia, stiamo semplicemente dichiarando metodi, la classe concreta che implementa queste due interfacce comprende che esiste un solo metodo (come hai descritto entrambi hanno lo stesso nome nel tipo restituito). quindi non dovrebbe esserci alcun problema con esso. Sarai in grado di definire quel metodo in una classe concreta.

Ma quando due interfacce hanno un metodo con lo stesso nome ma un tipo di ritorno diverso e si implementano due metodi in classe concreta:

Si prega di guardare sotto il codice:

public interface InterfaceA {
  public void print();
}


public interface InterfaceB {
  public int print();
}

public class ClassAB implements InterfaceA, InterfaceB {
  public void print()
  {
    System.out.println("Inside InterfaceA");
  }
  public int print()
  {
    System.out.println("Inside InterfaceB");
    return 5;
  }
}

quando il compilatore ottiene il metodo "public void print ()", cerca prima in InterfaceA e lo ottiene, ma dà comunque un errore di compilazione che il tipo restituito non è compatibile con il metodo di InterfaceB.

Quindi va in tilt per il compilatore.

In questo modo, non sarà possibile implementare due interfacce con un metodo con lo stesso nome ma con un tipo di ritorno diverso.


3

Beh, se sono entrambi uguali, non importa. Implementa entrambi con un unico metodo concreto per metodo di interfaccia.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.