web-dev-qa-db-fra.com

Expliquer le fonctionnement des variables masquées dans ce code Java

Considérons ci-dessous le code

class A
{
    int x = 5;
    void foo()
    {
        System.out.println(this.x);
    }
}
class B extends A
{
    int x = 6;
    // some extra stuff
}
class C
{
    public static void main(String args[])
    {
         B b = new B();
         System.out.println(b.x);
         System.out.println(((A)b).x);
         b.foo();
    }
 }  

La sortie du programme est 

6
5
5

Je comprends les deux premiers mais je n'arrive pas à comprendre le dernier. Comment b.foo () affiche-t-il 5. La classe B héritera de la méthode foo. Mais ne devrait-il pas imprimer ce que b.x imprimerait? Qu'est-ce qui se passe exactement ici?

21
Parzival

Oui, la classe B hérite de la méthode foo. Mais la variable x dans B masque la x dans A; cela ne le remplace pas.

C'est une question de portée. La méthode foo dans A ne voit que les variables qui sont dans la portée. La seule variable de portée est la variable d'instance x dans A.

La méthode foo est héritée, mais non substituée, dans B. Si vous deviez explicitement remplacer foo par le même code exact:

class B extends A
{
    int x = 6;

    @Override
    void foo()
    {
        System.out.println(this.x);
    }
}

Ensuite, la variable qui serait dans la portée lorsque this.x le désignerait serait B 's x, et 6 serait imprimé. Bien que le texte de la méthode soit identique, la référence est différente en raison de sa portée.

Incidemment, si vous voulez vraiment faire référence à A's x dans la classe B, vous pouvez utiliser super.x.

18
rgettman

Les champs ne peuvent pas être remplacés en Java et les sous-classes portant le même nom que la classe parente ombrent "uniquement" les champs de la classe parente.
Donc, this.x fait référence à la x définie dans la classe actuelle: A.
Attendu que le résultat: 5

Pour être plus précis: la méthode foo() est héritée par la sous-classe B mais cela ne signifie pas que le comportement de la méthode héritée va changer à propos des champs d'instance référencés puisque ces champs ne sont pas écrasables: l'expression this.x qui fait référence au champ A.x dans la méthode foo() continue à référencer A.x

C'est exactement la même chose que pour les deux déclarations précédentes: 

 B b = new B();
 System.out.println(b.x); // refers B.x -> 6
 System.out.println(((A)b).x); // refers A.x -> 5
 b.foo(); // refers under the hood A.x -> 5

La très bonne réponse de rgettman montre comment vous pouvez surmonter le champ qui se cache dans la sous-classe.
Une alternative pour surmonter le masquage consiste à créer le champ d’instance private (recommandé) et à fournir une méthode renvoyant la valeur.
De cette manière, vous bénéficiez du mécanisme dominant et le masquage de champ n’est plus un problème pour les clients des classes:

class A
{
    private int x = 5;

    int getX(){
        return x;
    }

    void foo()
    {
        System.out.println(this.getX());
    }
}
class B extends A
{
    private int x = 6;

    int getX(){
        return x;
    }
}
2
davidxxx

Eh bien, c'est à cause de la liaison statique.

1) La liaison statique en Java se produit pendant la compilation en dynamique la liaison a lieu pendant l'exécution.

2) méthodes privées, méthodes finales et méthodes statiques et variables utilise une liaison statique et est lié par le compilateur alors que les méthodes virtuelles sont lié au moment de l'exécution en fonction de l'objet d'exécution.

3) La liaison statique utilise des informations de type (Classe en Java) pour la liaison tandis que la liaison dynamique utilise Object pour résoudre la liaison.

4) Les méthodes surchargées sont liées à l'aide d'une liaison statique lorsqu'elles sont remplacées les méthodes sont liées à l'aide de la liaison dynamique au moment de l'exécution.

2
Michel_T.

Dans Java , les méthodes peuvent être remplacées alors que les variables ne le peuvent pas. Donc, comme votre méthode foo n'est pas remplacée dans B, elle prend la variable membre de A.

1
Anatolii

Quand vous appelez 

b.foo(); 

Il vérifie si B a remplacé la méthode foo(), ce qui n'est pas le cas. Il regarde alors un niveau supérieur, la super-classe A et appelle cette méthode.

Vous avez ensuite appelé la version de foo() de A qui l’affiche ensuite 

this.x

Maintenant, A ne peut pas voir la version de B de x.


Pour résoudre ce problème, vous devez remplacer la méthode dans B

class B extends A
{
    int x = 6;

    @Override
    void foo()
    {
        System.out.println(this.x);
    }

}

Maintenant, appelant

b.foo();

appellera la version B de foo() et vous obtiendrez le résultat attendu.

0
Andreas DM