Hello,
peut-être que ça aiderait de bien faire la différence entre ce qui se passe au moment de la compilation, et ce qui se passe au moment de l'exécution.
Comme son nom l'indique, ce qui est dynamique, c'est à l'exécution. Là, on est en train de manipuler des vrais objets en live, et l'implémentation de leurs comportement peut dépendre de quelle est leur classe réelle maintenant qu'on est bel et bien en train de les exécuter et qu'on les a sous la main pour connaître leur classe.
Par contre, tout ce qui se passe à la compilation, pendant qu'on est en train d'écrire le programme, ça c'est entièrement statique. Pendant la compilation, le programmeur sait peut-être, parce qu'il le déduit en lisant le programme, quelles seront les classes réelles des objets manipulés. Mais le compilateur, lui, n'en sait rien. Il n'a pas les objets disponibles pour vérifier leur type comme ce sera le cas à l'exécution.
Or, il faut bien comprendre que choisir quels champs on va lire/écrire, choisir quelle méthode on appelle, tout cela, c'est fait à la compilation. C'est statique.
Ce qui est dynamique, et qui peut dépendre des types réels, c'est quelle est l'implémentation réelle que fournit l'objet pour la méthode qu'on a décidé d'appeler. Certes les méthodes ont déjà été décidées à la compilation... Mais les sous-classes peuvent décider de redéfinir leurs méthodes : ce sont toujours les mêmes qu'on a décidé d'appeler, mais leur comportement est différent.
Tu dis que pour a2.f(b) => BA, tu ne comprends pas. Tu te dis que l'objet B sur lequel on appelle une méthode f(), ça devrait être sa méthode f(B), puisque le paramètre b est de type B.
Oui mais, quelle méthode est appelée, c'est décidé à la compilation. C'est à dire à un moment où on ne connaît pas les types réels des objets. Or l'objet a2 semble peut-être évidemment de type B, mais ça, ça ne sera connu qu'à l'exécution. À la compilation, il est déclaré comme ça :
A a2 = quelque chose qui sera évalué à l'exécution;
Imagine que B ne définisse pas de méthode f(B) mais à la place une méthode
1 2 3
| public String toto() {
return "toto";
} |
Tu crois vraiment que tu pourrais appeler a2.toto() ?
Eh ben non, puisque a2 est une variable de type A, et que la classe A n'a pas de méthode toto() que tu pourrais possiblement appeler.
C'est pareil. La classe A n'a pas non plus de méthode f(B). Elle a une méthode f(A) et une méthode f(D), donc c'est l'une des deux qui peut être appelée. Elle n'a pas de méthode f(B), et donc cette méthode qui n'existe pas ne sera pas appelée.
Celle qui est choisie n'est pas f(D) car le paramètre, b, est de type B qui n'est pas un sous-type de D.
Par contre, le type B est un sous-type de A, donc rien ne s'oppose à choisir la méthode f(A), ce qui est donc fait.
La compilation est faite, c'est décidé, c'est la méthode f(A) qui est appelée avec ce code.
Ensuite, à l'exécution, il se trouve que l'objet contenu dans la variable a2, est un objet de classe B. Et la classe B redéfinit la méthode f(A), celle qu'on a choisi d'appeler. C'est donc la méthode f(A) de la classe B qui est exécutée.
Même raisonnement pour les autres : la méthode qui sera appelée, ça se choisit à la compilation. L'implémentation de cette méthode par les objets sur lesquelles on l'appelle, ça dépend de quelle implémentation ces objets fournissent à l'exécution, du fait de leur classe réelle.
Partager