Implementieren von zwei Schnittstellen in einer Klasse mit derselben Methode. Welche Schnittstellenmethode wird überschrieben?

235

Zwei Schnittstellen mit denselben Methodennamen und Signaturen. Aber wie wird der Compiler dann von einer einzelnen Klasse implementiert, um festzustellen, welche Methode für welche Schnittstelle geeignet ist?

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;
  }
}   
Jothi
quelle

Antworten:

337

Wenn ein Typ zwei Schnittstellen implementiert und jeweils interfaceeine Methode mit identischer Signatur definiert, gibt es tatsächlich nur eine Methode, und sie sind nicht unterscheidbar. Wenn die beiden Methoden beispielsweise widersprüchliche Rückgabetypen haben, liegt ein Kompilierungsfehler vor. Dies ist die allgemeine Regel für Vererbung, Überschreiben, Ausblenden und Deklarieren von interfaceMethoden und gilt auch für mögliche Konflikte nicht nur zwischen zwei geerbten Methoden, sondern auch für eine interfaceund eine Supermethode classoder auch nur für Konflikte aufgrund der Typlöschung von Generika.


Kompatibilitätsbeispiel

Hier ist ein Beispiel, in dem Sie eine haben interface Gift, die eine present()Methode hat (wie in, Geschenke präsentieren), und auch eine interface Guest, die auch eine present()Methode hat (wie in, der Gast ist anwesend und nicht abwesend).

Presentable johnnyist sowohl a Giftals auch 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!!!"
    }
}

Das obige Snippet wird kompiliert und ausgeführt.

Beachten Sie, dass nur eine @Override erforderlich ist !!! . Dies liegt daran, dass Gift.present()und Guest.present()" @Overrideäquivalent" sind ( JLS 8.4.2 ).

Es gibt also johnny nur eine Implementierung von present(), und es spielt keine Rolle, wie Sie behandeln johnny, ob als Giftoder als Guest, es gibt nur eine Methode, die aufgerufen werden kann.


Beispiel für Inkompatibilität

Hier ist ein Beispiel, in dem die beiden geerbten Methoden NICHT @Override-äquivalent sind:

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

Dies bekräftigt weiter, dass das Erben von Mitgliedern von a interfacedie allgemeine Regel der Mitgliedererklärungen befolgen muss. Hier haben wir Giftund Guestdefinieren present()mit inkompatiblen Rückgabetypen: eine voidder anderen boolean. Aus dem gleichen Grund, aus dem Sie ein void present()und ein boolean present()in einem Typ nicht können, führt dieses Beispiel zu einem Kompilierungsfehler.


Zusammenfassung

Sie können Methoden erben, die @Override-äquivalent sind, vorbehaltlich der üblichen Anforderungen zum Überschreiben und Ausblenden von Methoden. Da sie SIND @Override -Äquivalent, effektiv ist es nur eine Methode zu implementieren, und somit gibt es nichts zu unterscheiden / Auswahl.

Der Compiler muss nicht identifizieren, welche Methode für welche Schnittstelle @Overridegeeignet ist , da sie, sobald festgestellt wurde, dass sie gleichwertig sind, dieselbe Methode sind.

Das Beheben potenzieller Inkompatibilitäten mag eine schwierige Aufgabe sein, aber das ist insgesamt ein anderes Problem.

Verweise

Polygenschmierstoffe
quelle
Danke - das war hilfreich. Ich hatte jedoch eine weitere Frage zur Inkompatibilität, die ich als neue Frage gestellt habe
Amaidment
2
Übrigens ändert sich dies ein wenig mit der Unterstützung von defaultMethoden in Java 8.
Peter Lawrey
Zusammengesetzte Klassen zur Behebung potenzieller Inkompatibilitäten mögen der Trick sein :), aber ich hatte nie ein solches Problem, und es ist immer noch offensichtlich, dass es passieren kann.
Wassermann Power
1
Dieser Artikel präsentiert ein Entwurfsmuster , die verwendet werden können , um etwas mit der Situation umzugehen , wo Sie brauchen zwei zu implementieren Schnittstelle Zusammenstoßen, sagen Foound Bar. Grundsätzlich muss Ihre Klasse beispielsweise eine der Schnittstellen implementieren Foound eine Bar asBar()Methode bereitstellen , um eine innere Klasse zurückzugeben, die die zweite BarSchnittstelle implementiert . Nicht perfekt, da Ihre Klasse letztendlich keine "Bar" ist, aber unter bestimmten Umständen nützlich sein kann.
Javaru
1
Ich
bin
25

Dies wurde als Duplikat dieser Frage markiert: /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

Sie benötigen Java 8, um ein Mehrfachvererbungsproblem zu erhalten, aber es ist immer noch kein Diamonproblem als solches.

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.

Wie JB Nizet kommentiert, können Sie dieses Problem beheben.

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

Sie haben jedoch kein Problem mit

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.
Peter Lawrey
quelle
Beeindruckend. das ist neu für mich. Warum mussten sie in Java 8 einen Standard erstellen?
Erran Morad
1
Um das Hinzufügen neuer Methoden zu Schnittstellen (insbesondere Sammlungsschnittstellen) zu erleichtern, ohne 60% der Codebasis zu beschädigen.
Tassos Bassoukos
@BoratSagdiyev Der Hauptgrund war, Abschlüsse zu unterstützen und die nützlicher zu machen. Siehe Collection.stream (). Schauen Sie sich List.sort () docs.oracle.com/javase/8/docs/api/java/util/… an. Sie haben eine Methode für alle Listen hinzugefügt, ohne eine bestimmte Implementierung ändern zu müssen. Sie fügten Collection.removeIf () hinzu, was nützlich ist
Peter Lawrey
@TassosBassoukos +1 sagen, Sie haben Ihre eigene Implementierung von List, jetzt können Sie myList.stream () it oder myList.sort () es ohne Änderung Ihres Codes
Peter Lawrey
3
@PeterLawrey: AB wird nicht kompiliert, da es überschrieben werden muss hi()(um die Mehrdeutigkeit zu beheben). Zum Beispiel, indem Sie es so A.super.hi()implementieren, dass es auf die gleiche Weise wie A. implementiert wird.
JB Nizet
20

Für den Compiler sind diese beiden Methoden identisch. Es wird eine Implementierung von beiden geben.

Dies ist kein Problem, wenn die beiden Methoden effektiv identisch sind, da sie dieselbe Implementierung haben sollten. Wenn sie vertraglich unterschiedlich sind (gemäß der Dokumentation für jede Schnittstelle), sind Sie in Schwierigkeiten.

Asche
quelle
2
Es erklärt , warum Java nicht zulässt , dass Sie sich mehr als eine Klasse
Arthur Ronald
1
@ ArthurRonald, eigentlich sieht es nur verwandt aus. Allerdings IMO - Klasse , die sich mehr als eine Klasse kann in Diamant - Problem führen (die in der am meisten abgeleitete Klasse duplizierten Objektzustand ist) , und das ist wahrscheinlich , warum Java seine Benutzer weg von den Mühen nahm. Andererseits kann eine Klasse, die mehr als eine Klasse implementiert, niemals auf Diamond Problem stoßen, nur weil die Schnittstelle Objekten keinen Status bietet. Das Problem ist ausschließlich auf Syntaxbeschränkungen zurückzuführen - die Unfähigkeit, den Funktionsaufruf vollständig zu qualifizieren.
Uvsmtid
13

Es gibt nichts zu identifizieren. Schnittstellen verbieten nur einen Methodennamen und eine Signatur. Wenn beide Schnittstellen eine Methode mit genau demselben Namen und derselben Signatur haben, kann die implementierende Klasse beide Schnittstellenmethoden mit einer einzigen konkreten Methode implementieren.

Wenn jedoch die semantischen Verträge der beiden Schnittstellenmethoden widersprüchlich sind, haben Sie ziemlich viel verloren. Sie können dann nicht beide Schnittstellen in einer Klasse implementieren.

Michael Borgwardt
quelle
4

Versuchen Sie, die Schnittstelle anonym zu implementieren.

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

}

}
dcanh121
quelle
4

Wie in der Schnittstelle deklarieren wir nur Methoden. Eine konkrete Klasse, die diese beiden Schnittstellen implementiert, versteht, dass es nur eine Methode gibt (wie Sie beschrieben haben, haben beide den gleichen Namen im Rückgabetyp). Es sollte also kein Problem damit geben. Sie können diese Methode in einer konkreten Klasse definieren.

Wenn jedoch zwei Schnittstellen eine Methode mit demselben Namen, aber unterschiedlichem Rückgabetyp haben und Sie zwei Methoden in einer konkreten Klasse implementieren:

Bitte schauen Sie sich den folgenden Code an:

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

Wenn der Compiler die Methode "public void print ()" erhält, sucht er zuerst in InterfaceA und erhält sie. Trotzdem gibt er einen Fehler bei der Kompilierung, dass der Rückgabetyp nicht mit der Methode von InterfaceB kompatibel ist.

Für den Compiler geht es also durcheinander.

Auf diese Weise können Sie nicht zwei Schnittstellen mit derselben Methode, sondern unterschiedlichem Rückgabetyp implementieren.

Bhagrav Jain
quelle
3

Nun, wenn beide gleich sind, spielt es keine Rolle. Beide werden mit einer einzigen konkreten Methode pro Schnittstellenmethode implementiert.

Paul Whelan
quelle