Liaison dynamique Java et remplacement de méthode

89

Hier, j'ai eu un entretien téléphonique technique de deux heures (que j'ai réussi, woohoo!), Mais j'ai complètement étouffé la question suivante concernant la liaison dynamique en Java. Et c'est doublement déroutant parce que j'avais l'habitude d'enseigner ce concept aux étudiants de premier cycle quand j'étais TA il y a quelques années, donc la perspective que je leur ai donné de la désinformation est un peu dérangeante ...

Voici le problème qui m'a été posé:

/* What is the output of the following program? */

public class Test {

  public boolean equals( Test other ) {
    System.out.println( "Inside of Test.equals" );
    return false;
  }

  public static void main( String [] args ) {
    Object t1 = new Test();
    Object t2 = new Test();
    Test t3 = new Test();
    Object o1 = new Object();

    int count = 0;
    System.out.println( count++ );// prints 0
    t1.equals( t2 ) ;
    System.out.println( count++ );// prints 1
    t1.equals( t3 );
    System.out.println( count++ );// prints 2
    t3.equals( o1 );
    System.out.println( count++ );// prints 3
    t3.equals(t3);
    System.out.println( count++ );// prints 4
    t3.equals(t2);
  }
}

J'ai affirmé que la sortie aurait dû être deux instructions d'impression distinctes issues de la equals()méthode remplacée : at t1.equals(t3)et t3.equals(t3). Le dernier cas est assez évident, et avec le premier cas, même s'il t1a une référence de type Object, il est instancié en tant que type Test, donc la liaison dynamique doit appeler la forme surchargée de la méthode.

Apparemment non. Mon enquêteur m'a encouragé à exécuter le programme moi-même, et voilà, il n'y avait qu'une seule sortie de la méthode remplacée: à la ligne t3.equals(t3).

Ma question est alors: pourquoi? Comme je l'ai déjà mentionné, même s'il t1s'agit d'une référence de type Object (de sorte que la liaison statique appellerait la equals()méthode Object ), la liaison dynamique devrait prendre soin d'invoquer la version la plus spécifique de la méthode en fonction du type instancié de la référence. Qu'est-ce que je rate?

Magsol
la source
Veuillez trouver mon message à cette réponse où j'ai fait de mon mieux pour expliquer avec des cas supplémentaires. J'apprécierais vraiment vos contributions :)
Devendra Lattu

Réponses:

81

Java utilise la liaison statique pour les méthodes surchargées et la liaison dynamique pour les méthodes remplacées. Dans votre exemple, la méthode equals est surchargée (a un type de paramètre différent de Object.equals ()), donc la méthode appelée est liée au type de référence au moment de la compilation.

Quelques discussions ici

Le fait qu'il s'agisse de la méthode égale n'est pas vraiment pertinent, à part que c'est une erreur courante de la surcharger au lieu de la remplacer, ce dont vous êtes déjà conscient en fonction de votre réponse au problème lors de l'entretien.

Edit: Une bonne description ici aussi. Cet exemple montre plutôt un problème similaire lié au type de paramètre, mais causé par le même problème.

Je crois que si la liaison était réellement dynamique, alors tout cas où l'appelant et le paramètre étaient une instance de Test entraînerait l'appel de la méthode remplacée. Donc t3.equals (o1) serait le seul cas qui ne serait pas imprimé.

Robin
la source
Beaucoup de gens soulignent qu'il est surchargé et non remplacé, mais même avec cela, vous vous attendez à ce qu'il résout correctement le problème surchargé. Votre message est en fait le seul à ce jour qui réponde correctement à la question pour autant que je sache.
Bill K
4
Mon erreur a complètement manqué le fait que la méthode est en effet surchargée plutôt que remplacée. J'ai vu "equals ()" et j'ai immédiatement pensé hérité et remplacé. On dirait que j'ai, encore une fois, compris le concept plus large et plus difficile, mais j'ai foiré les détails simples. : P
Magsol
14
une autre raison pour laquelle l'annotation @Override existe.
Matt
1
Répétez après moi: "Java utilise la liaison statique pour les méthodes surchargées et la liaison dynamique pour les méthodes remplacées" - +1
Mr_and_Mrs_D
1
alors j'ai obtenu mon diplôme sans le savoir. Merci!
Atieh
25

La equalsméthode de Testne remplace pas la equalsméthode de java.lang.Object. Regardez le type de paramètre! La Testclasse est en surcharge equalsavec une méthode qui accepte un Test.

Si la equalsméthode est destinée à remplacer, elle doit utiliser l'annotation @Override. Cela provoquerait une erreur de compilation pour signaler cette erreur courante.

Erickson
la source
Oui, je ne sais pas trop pourquoi j'ai manqué ce détail simple mais crucial, mais c'est exactement là que se trouvait mon problème. Je vous remercie!
Magsol
+1 pour être la vraie réponse aux résultats curieux du questionneur
matt b
Veuillez trouver mon message à cette réponse où j'ai fait de mon mieux pour expliquer avec des cas supplémentaires. J'apprécierais vraiment vos contributions :)
Devendra Lattu
6

Fait intéressant, dans le code Groovy (qui pourrait être compilé dans un fichier de classe), tous les appels sauf un exécuteraient l'instruction d'impression. (Celui qui compare un test à un objet n'appellera clairement pas la fonction Test.equals (Test).) C'est parce que groovy fait un typage complètement dynamique. Ceci est particulièrement intéressant car il ne contient aucune variable explicitement typée dynamiquement. J'ai lu à plusieurs endroits que cela était considéré comme dangereux, car les programmeurs s'attendent à ce que groovy fasse le truc java.

Benson
la source
1
Malheureusement, le prix que Groovy paie pour cela est un énorme impact sur les performances, car chaque invocation de méthode utilise la réflexion. S'attendre à ce qu'une langue fonctionne exactement de la même manière qu'une autre est généralement considéré comme nuisible. Il faut être conscient des différences.
Joachim Sauer
Devrait être agréable et rapide avec invokedynamic dans JDK7 (ou même en utilisant une technique d'implémentation similaire aujourd'hui).
Tom Hawtin - tackline
5

Java ne prend pas en charge la co-variance dans les paramètres, uniquement dans les types de retour.

En d'autres termes, alors que votre type de retour dans une méthode de substitution peut être un sous-type de ce qu'il était dans le remplacement, ce n'est pas vrai pour les paramètres.

Si votre paramètre pour equals dans Object est Object, placer un equals avec n'importe quoi d'autre dans une sous-classe sera une méthode surchargée et non remplacée. Par conséquent, la seule situation où cette méthode sera appelée est lorsque le type statique du paramètre est Test, comme dans le cas de T3.

Bonne chance avec le processus d'entretien d'embauche! J'aimerais être interviewé dans une entreprise qui pose ces types de questions au lieu des questions habituelles sur les algo / structures de données que j'enseigne à mes étudiants.

Uri
la source
1
Vous voulez dire des paramètres contravariants.
Tom Hawtin - tackline
J'ai en quelque sorte complètement passé sous silence le fait que différents paramètres de méthode créent intrinsèquement une méthode surchargée, pas une méthode remplacée. Oh, ne vous inquiétez pas, il y avait aussi des questions sur les algo / structures de données. : P Et merci pour la bonne chance, j'en ai besoin! :)
Magsol
4

Je pense que la clé réside dans le fait que la méthode equals () n'est pas conforme à la norme: elle prend un autre objet Test, pas un objet Object et ne remplace donc pas la méthode equals (). Cela signifie que vous ne l'avez en fait surchargé pour faire quelque chose de spécial que lorsqu'il lui est donné un objet Test tout en lui donnant des appels d'objet Object.equals (Object o). La recherche de ce code dans n'importe quel IDE devrait vous montrer deux méthodes equals () pour Test.

P Arrayah
la source
Ceci, et la plupart des réponses manquent le point. Le problème ne réside pas dans le fait que la surcharge est utilisée au lieu du dépassement. C'est pourquoi la méthode surchargée n'est pas utilisée pour t1.equals (t3), lorsque t1 est déclaré comme Object mais initialisé à Test.
Robin
4

La méthode est surchargée au lieu d'être surchargée. Equals prend toujours un objet comme paramètre.

btw, vous avez un élément à ce sujet dans le java efficace de Bloch (que vous devriez posséder).

Gilles
la source
Java efficace de Joshua Bloch?
DJClayworth
Efficace oui, je pensais à autre chose en tapant: D
Gilles
4

Quelques remarques dans Dynamic Binding (DD) et Static Binding̣̣̣ (SB) après une recherche un certain temps:

1. exécution de la synchronisation : (Ref.1)

  • DB: au moment de l'exécution
  • SB: temps du compilateur

2. utilisé pour :

  • DB: priorité
  • SB: surcharge (statique, privée, définitive) (Réf.2)

Référence:

  1. Exécuter le résolveur de moyenne quelle méthode préfère utiliser
  2. Parce que ne peut pas remplacer la méthode avec le modificateur statique, privé ou final
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html
NguyenDat
la source
2

Si une autre méthode est ajoutée qui remplace au lieu de surcharger, elle expliquera l'appel de liaison dynamique au moment de l'exécution.

/ * Quelle est la sortie du programme suivant? * /

public class DynamicBinding {
    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside @override: this is dynamic binding");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++);// prints 0
        t1.equals(t2);
        System.out.println(count++);// prints 1
        t1.equals(t3);
        System.out.println(count++);// prints 2
        t3.equals(o1);
        System.out.println(count++);// prints 3
        t3.equals(t3);
        System.out.println(count++);// prints 4
        t3.equals(t2);
    }
}
Prabu R
la source
0

La réponse à la question "pourquoi?" c'est ainsi que le langage Java est défini.

Pour citer l'article de Wikipédia sur la covariance et la contravariance :

La covariance du type de retour est implémentée dans la version J2SE 5.0 du langage de programmation Java. Les types de paramètres doivent être exactement les mêmes (invariants) pour la substitution de méthode, sinon la méthode est surchargée avec une définition parallèle à la place.

Les autres langues sont différentes.

Ykaganovich
la source
Mon problème était à peu près équivalent à voir 3 + 3 et écrire 9, puis voir 1 + 1 et écrire 2. Je comprends comment le langage Java est défini; dans ce cas, pour une raison quelconque, j'ai complètement confondu la méthode avec quelque chose qu'elle n'était pas, même si j'ai évité cette erreur ailleurs dans le même problème.
Magsol
0

Il est très clair qu'il n'y a pas de concept de priorité ici. C'est une surcharge de méthode. la Object()méthode de la classe Object prend le paramètre de référence de type Object et cette equal()méthode prend le paramètre de référence de type Test.

Ankush gatfane
la source
-1

Je vais essayer d'expliquer cela à travers deux exemples qui sont les versions étendues de certains des exemples que j'ai rencontrés en ligne.

public class Test {

    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside of Test.equals ot type Object");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++); // prints 0
        o1.equals(t2);

        System.out.println("\n" + count++); // prints 1
        o1.equals(t3);

        System.out.println("\n" + count++);// prints 2
        t1.equals(t2);

        System.out.println("\n" + count++);// prints 3
        t1.equals(t3);

        System.out.println("\n" + count++);// prints 4
        t3.equals(o1);

        System.out.println("\n" + count++);// prints 5
        t3.equals(t3);

        System.out.println("\n" + count++);// prints 6
        t3.equals(t2);
    }
}

Ici, pour les lignes avec des valeurs de comptage 0, 1, 2 et 3; nous avons la référence d' Object pour o1 et t1 sur la equals()méthode. Ainsi, au moment de la compilation, la equals()méthode du fichier Object.class sera délimitée.

Cependant, même si la référence de t1 est Object , elle a l' initialisation de la classe Test .
Object t1 = new Test();.
Par conséquent, au moment de l'exécution, il appelle le public boolean equals(Object other)qui est un

méthode remplacée

. entrez la description de l'image ici

Maintenant, pour les valeurs de comptage 4 et 6, il est à nouveau simple que t3 qui a la référence et l' initialisation de Test appelle la equals()méthode avec le paramètre comme références d'objet et est un

méthode surchargée

D'ACCORD!

Encore une fois, pour mieux comprendre quelle méthode le compilateur appellera, il suffit de cliquer sur la méthode et Eclipse mettra en évidence les méthodes de types similaires qu'il pense appeler au moment de la compilation. Si elle n'est pas appelée au moment de la compilation, ces méthodes sont un exemple de remplacement de méthode.

entrez la description de l'image ici

Devendra Lattu
la source