J'étudie la CS et nous avons des questions sur le polymorphisme que je n'arrive pas à comprendre. Voici un exemple:
public class AA{
public AA(){
foo();
}
private void foo() {
System.out.print("AA::foo ");
goo();
}
public void goo(){
System.out.print("AA::goo ");
}
}
public class BB extends AA{
public BB(){
foo();
}
public void foo(){
System.out.print("BB:foo ");
}
public void goo(){
System.out.print("BB::goo ");
}
public static void main(String[] args){
// Code goes here
}
}
Quand dans void principal j'ajoute la ligne:
AA a = new BB();
il commence par imprimer un constructeur AA: foo mais ensuite goo () l’envoie à BB, alors pourquoi?
Un polymorphisme simple tel que "Animal -> chat/araignée/chien" est facile à comprendre, mais en ce qui me concerne, je suis simplement perdu. Pouvez-vous me donner des conseils pour lire ce code? Quelles sont les règles?
EDIT: il n'y a pas d'annotation @Override
car il s'agit d'une question d'examen.
public class AA {
private void foo() { ... }
^^^^^^^
}
Le polymorphisme n'est pas appliqué aux méthodes private
. Une sous-classe n'hérite pas des méthodes private
, elles ne peuvent donc pas être remplacées:
Une classe
C
hérite de sa superclasse directe toutes les méthodes concrètesm
(statiques et instances) de la superclasse pour lesquelles toutes les conditions suivantes sont vraies:
m
est un membre de la superclasse directe deC
.m
estpublic
,protected
ou est déclaré avec un accès de package dans le même package queC
.- Aucune méthode déclarée dans
C
n'a une signature qui est une sous-signature de la signature dem
.Spécification du langage Java - 8.4.8. Héritage, dérogation et dissimulation
Par conséquent, l'appel foo()
du constructeur A
n'appelle pas BB#foo
, il appelle AA#foo
.
Mais l'appel goo()
dans AA#foo
fait référence à la méthode remplacée BB#goo
. Ici, avec les méthodes public
, les méthodes de substitution et de polymorphisme ont été appliquées.
C'est un peu délicat, je vous recommande donc de mettre l'annotation @Override
partout où elle est supposée être.
public class BB extends AA {
@Override // it doesn't compile - no overriding here
public void foo() { ... }
@Override // it does override
public void goo() { ... }
}
Il pourrait également être utile de détecter un autre problème:
Parfois, les programmeurs surchargent une déclaration de méthode quand ils entendent la surcharger, ce qui entraîne des problèmes subtils. Le type d'annotation
Override
prend en charge la détection précoce de tels problèmes.Si une déclaration de méthode dans le type
T
est annotée avec@Override
, mais que la méthode ne remplace pas parT
une méthode déclarée dans un supertype deT
ou si elle n'est pas équivalente à une méthode publique deObject
, une erreur de compilation survient.
Si un corps de constructeur ne commence pas par une invocation explicite de constructeur et que le constructeur déclaré ne fait pas partie de la classe primordiale
Object
, le corps de constructeur commence implicitement par une invocation de constructeur de super-classesuper();
, invocation du constructeur de son superclasse directe qui ne prend aucun argument.Spécification du langage Java - 8.8.7. Corps de constructeur
Pour le dire simplement,
public BB() {
foo();
}
se transforme en
public BB() {
super();
foo();
}
En gardant à l'esprit super();
, nous pouvons faire l'illustration suivante:
new BB()
AA() // super(); -> AA constructor
A#foo() // private method call
B#goo() // polymorphic method call
BB() // BB constructor
B#foo() // plain method call
C'est très bien expliqué dans la documentation officielle:
https://docs.Oracle.com/javase/tutorial/Java/IandI/super.html
Si un constructeur n'appelle pas explicitement un constructeur de superclasse, le compilateur Java insère automatiquement un appel au constructeur sans argument de la superclasse. Si la super classe n'a pas de constructeur sans argument, vous obtiendrez une erreur lors de la compilation. Object a un tel constructeur, donc si Object est la seule superclasse, il n'y a pas de problème.
Ainsi, le compilateur Java ajoute super () sans arguments pour vous.
En fait, si la classe que vous étendez n'a pas de constructeur par défaut, vous devrez préalablement appeler ce constructeur avec args.
Sinon, la raison pour laquelle AA:goo
n'est pas appelé est parce qu'elle est remplacée par BB
même si l'annotation @Override
n'est pas définie, si vous voulez voir cet appel, vous devez utiliser super (); dans votre méthode b: goo. En fait, le foo n'est pas substitué car il est privé, il n'est donc pas possible de le remplacer. Si vous essayez d'ajouter l'annotation @Override, vous aurez un échec de compilation.
Il existe également un grave défaut de conception dans l'exemple de code: il appelle une méthode pouvant être remplacée par un constructeur. Cela signifie que l'objet BB peut ne pas être complètement initialisé au moment où cette méthode est appelée. Par exemple:
public class AA{
public AA(){
foo();
}
private void foo() {
System.out.print("AA::foo ");
goo();
}
public void goo(){
System.out.print("AA::goo ");
}
}
public class BB extends AA{
private Date timestamp;
public BB() {
super();
foo();
timestamp = new Date();
}
public void foo() {
System.out.print("BB:foo ");
}
public void goo() {
// goo() gets called before timestamp is initialized
// causing a NullPointerException
System.out.print("BB::goo " + timestamp.getYear());
}
public static void main(String[] args){
AA obj = new BB();
}
}
Rappelez-vous ceci: N'APPUYEZ JAMAIS UNE MÉTHODE ANNULABLE D'UN CONSTRUCTEUR (même indirectement comme dans cet exemple)
AA :: foo () est privé, donc AA :: foo () et BB :: foo () ne sont pas identiques.
new BB()
appellera d’abord AA :: Constructor, en appelant AA :: foo ().
AA :: foo () appelez goo () et puisque vous avez instancié la classe BB, ce sera BB :: goo ().
Veuillez utiliser le mot-clé "override" sur la méthode lorsque vous souhaitez faire quelque chose comme ceci.
Le polymorphisme est simple mais déroutant lorsque nous utilisons différents ensembles de noms [ce qui est le cas ici].
Le polymorphisme est fondamentalement une relation parent-enfant. La clé est la suivante: si vous vous efforcez de placer les noms des classes, utilisez-vous vous-même, par exemple, donnez une ligne de commentaire à côté des noms de classe, comme indiqué ci-dessous
public class AA{} //your Parent name
public class BB extends AA{} // yourself i.e. your name
AA a = new BB();
, décodez le code comme suit:
BB c'est toi, AA est ton parent.
new
mot-clé est avec VOUS (c'est-à-dire BB), ainsi un nouvel objet de VOUS serait créé ou né. Pour que vous puissiez naître, sans vos parents (AA), vous ne pouvez pas exister et, par conséquent, ils seront d'abord nés ou créés (le constructeur AA serait exécuté). Une fois que vos parents (c.-à-d. AA) ont été créés, il est temps pour vous de naître (c.-à-d. Que le constructeur BB s'exécute).
Dans votre exemple
public AA(){
foo(); -- line A
}
private void foo() {
System.out.print("AA::foo ");
goo(); -- line B
}
public void goo(){
System.out.print("AA::goo "); -- line C
}
Comme je l’ai dit plus tôt, Line A serait appelée lorsque vous dites AA a = new BB();
, car Line A est dans le constructeur de AA, Line A appelle la méthode foo () et le contrôle atterrit donc dans foo ( ), affiche "AA :: toto" et exécute Ligne B. Line B appelle la méthode goo () et ainsi de suite elle atteint Line C. Une fois que Line C est exécuté, il ne reste plus rien à exécuter dans le constructeur AA (c’est-à-dire que l’objet est créé) et le contrôle est transmis au constructeur enfant (lorsque le parent est créé, il est temps que l’enfant naisse) et ainsi, l'enfant Constructor serait appelé ensuite.
Pour les étudiants/débutants, je vous recommande fortement de passer par Head First Java Edition. Cela vous aide vraiment à poser la Java Foundation Strong.