web-dev-qa-db-fra.com

Je n'arrive pas à comprendre le polymorphisme complexe

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.

16
Ron Kantor

Explication

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ètes m (statiques et instances) de la superclasse pour lesquelles toutes les conditions suivantes sont vraies:

  • m est un membre de la superclasse directe de C.
  • m est public, protected ou est déclaré avec un accès de package dans le même package que C.
  • Aucune méthode déclarée dans C n'a une signature qui est une sous-signature de la signature de m.

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 par T une méthode déclarée dans un supertype de T ou si elle n'est pas équivalente à une méthode publique de Object, une erreur de compilation survient.

Spécification du langage Java - 9.6.4.4. @Override

Illustration

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-classe super();, 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 
15
Andrew Tobilko

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.

3
Pau

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)

1
Adriaan Koster

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.

1
Magnas

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.

0
Jayanth