Implémentation de deux interfaces dans une classe avec la même méthode. Quelle méthode d'interface est remplacée?

235

Deux interfaces avec les mêmes noms de méthode et signatures. Mais implémenté par une seule classe, alors comment le compilateur identifiera quelle méthode est pour quelle interface?

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
la source

Réponses:

337

Si un type implémente deux interfaces, et chacune interfacedéfinit une méthode qui a une signature identique, alors en fait il n'y a qu'une seule méthode, et elles ne sont pas distinguables. Si, par exemple, les deux méthodes ont des types de retour en conflit, ce sera une erreur de compilation. Il s'agit de la règle générale d'héritage, de substitution de méthode, de masquage et de déclarations.Elle s'applique également aux conflits possibles non seulement entre 2 interfaceméthodes héritées , mais aussi à une interfaceet à une super classméthode, ou même aux conflits dus à l'effacement de type de génériques.


Exemple de compatibilité

Voici un exemple où vous avez un interface Gift, qui a une present()méthode (comme dans, présenter des cadeaux), et aussi un interface Guest, qui a également une present()méthode (comme dans, l'invité est présent et non absent).

Presentable johnnyest à la fois un Giftet un 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!!!"
    }
}

L'extrait ci-dessus se compile et s'exécute.

Notez qu'il n'y en a qu'un seul @Override nécessaire !!! . En effet, Gift.present()et Guest.present()sont " @Overrideéquivalentes" ( JLS 8.4.2 ).

Ainsi, il johnny n'y a qu'une seule implémentation de present(), et peu importe comment vous traitez johnny, que ce soit en tant que Giftou en tant que Guest, il n'y a qu'une seule méthode à invoquer.


Exemple d'incompatibilité

Voici un exemple où les deux méthodes héritées ne sont PAS @Overrideéquivalentes:

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

Cela réitère en outre que l'héritage des membres d'un interfacedoit obéir à la règle générale des déclarations de membres. Ici, nous avons Giftet Guestdéfinissons present()avec des types de retour incompatibles: l'un voidl'autre boolean. Pour la même raison que vous ne pouvez pas utiliser un void present()et un boolean present()dans un type, cet exemple entraîne une erreur de compilation.


Résumé

Vous pouvez hériter de méthodes @Overrideéquivalentes, sous réserve des exigences habituelles de remplacement et de masquage des méthodes. Puisqu'ils SONT @Override équivalents, il n'y a en fait qu'une seule méthode à implémenter, et donc il n'y a rien à distinguer / sélectionner.

Le compilateur n'a pas à identifier quelle méthode est pour quelle interface, car une fois qu'ils sont déterminés comme étant @Overrideéquivalents, ils sont la même méthode.

Résoudre les incompatibilités potentielles peut être une tâche délicate, mais c'est un autre problème.

Références

polygénelubrifiants
la source
Merci - c'était utile. Cependant, j'avais une autre question sur l'incompatibilité, que j'ai publiée en tant que nouvelle question
amaidment
2
BTW Cela change un peu avec le support des defaultméthodes en Java 8.
Peter Lawrey
Les classes composites pour résoudre les incompatibilités potentielles peuvent être l'astuce :), mais, je n'ai jamais eu un tel problème, et il est toujours évident que cela peut arriver.
Aquarius Power
1
Cet article présente un modèle de conception qui peut être utilisé pour traiter quelque peu la situation dans laquelle vous devez implémenter deux interfaces de collision, disons Fooet Bar. Fondamentalement, votre classe implémente l'une des interfaces, par exemple Foo, et fournit une Bar asBar()méthode pour renvoyer une classe interne qui implémente la deuxième Barinterface. Pas parfait puisque votre classe n'est finalement pas "un bar", mais cela pourrait être utile dans certaines circonstances.
Javaru
1
im un développeur java mais c # est vraiment plus intelligent à ce sujet: stackoverflow.com/questions/2371178/…
Amir Ziarati
25

Cela a été marqué comme un double de cette question /programming/24401064/understanding-and-solving-the-diamond-problems-in-java

Vous avez besoin de Java 8 pour obtenir un problème d'héritage multiple, mais ce n'est toujours pas un problème de diamant en tant que tel.

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.

Comme le commente JB Nizet, vous pouvez résoudre ce problème.

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

Cependant, vous n'avez pas de problème avec

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
la source
sensationnel. C'est nouveau pour moi. Pourquoi ont-ils dû créer par défaut dans java 8?
Erran Morad du
1
Pour faciliter l'ajout de nouvelles méthodes aux interfaces (en particulier les interfaces de collections) sans casser 60% de la base de code.
Tassos Bassoukos
@BoratSagdiyev La principale raison était de soutenir les fermetures et de les rendre plus utiles. Voir Collection.stream (). Jetez un œil à List.sort () docs.oracle.com/javase/8/docs/api/java/util/… Ils ont ajouté une méthode pour toutes les listes, sans avoir à modifier une implémentation spécifique. Ils ont ajouté Collection.removeIf () qui est utile
Peter Lawrey
@TassosBassoukos +1 dit que vous avez votre propre implémentation de List, maintenant vous pouvez le myList.stream () ou myList.sort () sans changer votre code
Peter Lawrey
3
@PeterLawrey: AB ne compilera pas car il doit remplacer hi()(pour corriger l'ambiguïté). Par exemple, en l'implémentant de manière A.super.hi()à choisir de l'implémenter de la même manière que A.
JB Nizet
20

En ce qui concerne le compilateur, ces deux méthodes sont identiques. Il y aura une mise en œuvre des deux.

Ce n'est pas un problème si les deux méthodes sont effectivement identiques, en ce sens qu'elles devraient avoir la même implémentation. S'ils sont contractuellement différents (selon la documentation de chaque interface), vous aurez des ennuis.

Cendre
la source
2
Cela explique pourquoi Java ne vous permet pas d' étendre plus d'une classe
Arthur Ronald
1
@ArthurRonald, en fait, cela semble juste lié. Cependant, IMO, une classe qui étend plus d'une classe peut s'exécuter dans Diamond Problem (qui est l'état d'objet dupliqué dans la classe la plus dérivée) et c'est probablement pourquoi Java a éloigné ses utilisateurs des problèmes. D'un autre côté, une classe qui implémente plusieurs classes ne peut jamais rencontrer Diamond Problem simplement parce que l'interface ne fournit pas d'état aux objets. Et le problème est purement dû aux limitations de syntaxe - incapacité à qualifier pleinement l'appel de fonction.
uvsmtid
13

Il n'y a rien à identifier. Les interfaces proscrivent uniquement un nom et une signature de méthode. Si les deux interfaces ont une méthode ayant exactement le même nom et la même signature, la classe d'implémentation peut implémenter les deux méthodes d'interface avec une seule méthode concrète.

Cependant, si les contrats sémantiques des deux méthodes d'interface se contredisent, vous avez à peu près perdu; vous ne pouvez alors pas implémenter les deux interfaces dans une seule classe.

Michael Borgwardt
la source
4

Essayez d'implémenter l'interface comme anonyme.

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
la source
4

Comme dans l'interface, nous ne faisons que déclarer des méthodes, la classe concrète qui implémente ces deux interfaces comprend qu'il n'y a qu'une seule méthode (comme vous l'avez décrit les deux ont le même nom dans le type de retour). il ne devrait donc pas y avoir de problème. Vous pourrez définir cette méthode dans une classe concrète.

Mais lorsque deux interfaces ont une méthode avec le même nom mais un type de retour différent et que vous implémentez deux méthodes dans une classe concrète:

Veuillez regarder le code ci-dessous:

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

quand le compilateur obtient la méthode "public void print ()" il regarde d'abord dans InterfaceA et il l'obtient. Mais il donne quand même une erreur de temps de compilation que le type de retour n'est pas compatible avec la méthode d'InterfaceB.

Il en va de même pour le compilateur.

De cette façon, vous ne pourrez pas implémenter deux interfaces ayant une méthode de même nom mais de type de retour différent.

Bhagrav Jain
la source
3

Eh bien, s'ils sont tous les deux identiques, cela n'a pas d'importance. Il implémente les deux avec une seule méthode concrète par méthode d'interface.

Paul Whelan
la source