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 inquiétante ...
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 de la méthode equals()
remplacée: à t1.equals(t3)
et t3.equals(t3)
. Le dernier cas est assez évident, et avec le premier cas, même si t1
A une référence de type Object, il est instancié comme type Test, donc la liaison dynamique doit appeler la forme redéfinie de la méthode.
Apparemment non. Mon intervieweur m'a encouragé à exécuter le programme moi-même, et voilà, il n'y avait qu'une seule sortie de la méthode redéfinie: à la ligne t3.equals(t3)
.
Ma question est alors, pourquoi? Comme je l'ai déjà mentionné, même si t1
Est une référence de type Object (donc la liaison statique invoquerait la méthode equals()
de l'Object), la liaison dynamique devrait se charger d'invoquer la version la plus spécifique de la méthode basée sur le type instancié de la référence. Qu'est-ce que je rate?
Java utilise une liaison statique pour les méthodes surchargées et une 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 à la référence tapez au moment de la compilation.
Quelques discussions ici
Le fait qu'il s'agisse de la méthode des égaux n'est pas vraiment pertinent, à part qu'il s'agit d'une erreur courante de surcharger au lieu de la remplacer, ce que vous savez déjà en fonction de votre réponse au problème lors de l'entretien.
Edit: Une bonne description ici aussi. Cet exemple montre un problème similaire lié au type de paramètre à la place, mais causé par le même problème.
Je crois que si la liaison était en fait dynamique, alors tout cas où l'appelant et le paramètre étaient une instance de Test entraînerait l'appel de la méthode substituée. Donc t3.equals (o1) serait le seul cas qui ne s'imprimerait pas.
La méthode equals
de Test
ne remplace pas la méthode equals
de Java.lang.Object
. Regardez le type de paramètre! La classe Test
surcharge equals
avec une méthode qui accepte un Test
.
Si la méthode equals
est destinée à remplacer, elle doit utiliser l'annotation @Override. Cela entraînerait une erreur de compilation pour signaler cette erreur courante.
Chose intéressante, dans le code Groovy (qui pourrait être compilé dans un fichier de classe), tous les appels sauf un exécuteraient l'instruction print. (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 à quelques endroits que cela est considéré comme dangereux, car les programmeurs s'attendent à ce que groovy fasse la chose Java.
Java ne prend pas en charge la co-variance dans les paramètres, uniquement dans les types de retour.
En d'autres termes, bien que votre type de retour dans une méthode de remplacement puisse ê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 d'égalité dans Object est Object, le fait de mettre un égal à 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'entrevue d'emploi! J'aimerais être interviewé dans une entreprise qui pose ce type de questions au lieu des questions habituelles d'algo/structures de données que j'enseigne à mes étudiants.
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é que pour faire quelque chose de spécial lorsqu'il reçoit l'objet Test tout en lui donnant l'objet Object appelle Object.equals (Object o). La recherche de ce code dans tout IDE devrait vous montrer deux méthodes equals () pour Test.
La méthode est surchargée au lieu d'être remplacée. Les égaux prennent toujours un objet comme paramètre.
btw, vous avez un élément à ce sujet dans Bloch's effective Java (que vous devriez posséder).
Quelques notes dans Liaison dynamique (DD) et Liaison statiqueic (SB) après une recherche un moment:
1.Timing execute : (Ref.1)
2.Utilisé pour :
Référence:
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);
}
}
J'ai trouvé un article intéressant sur la liaison dynamique vs statique. Il est livré avec un morceau de code pour simuler la liaison dynamique. Cela a rendu mon code plus lisible.
La réponse à la question "pourquoi?" c'est ainsi que le langage Java est défini.
Pour citer le article Wikipédia sur la covariance et la contravariance :
La covariance du type de retour est implémentée dans le Java version du langage de programmation J2SE 5.0. 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.
Il est très clair qu'il n'y a aucun concept de dérogation ici. C'est la surcharge de méthode. la méthode Object()
de la classe Object prend paramètre de référence de type Object et cette méthode equal()
prend paramètre de référence de type Test.
Voir aussi cette SO question, étroitement liée: remplacement de la Java équivaut à la méthode bizarrerie