J'étudie les fonctions de membre dominantes en Java et je pense à expérimenter avec des variables de membre dominantes.
Alors, j'ai défini les classes
public class A{
public int intVal = 1;
public void identifyClass()
{
System.out.println("I am class A");
}
}
public class B extends A
{
public int intVal = 2;
public void identifyClass()
{
System.out.println("I am class B");
}
}
public class mainClass
{
public static void main(String [] args)
{
A a = new A();
B b = new B();
A aRef;
aRef = a;
System.out.println(aRef.intVal);
aRef.identifyClass();
aRef = b;
System.out.println(aRef.intVal);
aRef.identifyClass();
}
}
La sortie est:
1
I am class A
1
I am class B
Je n'arrive pas à comprendre pourquoi, quand aRef est défini sur b, intVal est-il toujours de classe A?
Lorsque vous créez une variable du même nom dans une sous-classe, cela s'appelle hiding. La sous-classe résultante aura désormais les propriétés les deux. Vous pouvez accéder à celui de la superclasse avec super.var
ou ((SuperClass)this).var
. Les variables ne doivent même pas être du même type; ce ne sont que deux variables partageant un nom, un peu comme deux méthodes surchargées.
Les variables ne sont pas polymorphes en Java; ils ne se substituent pas les uns aux autres.
Les variables sont résolues au moment de la compilation, les méthodes au moment de l'exécution. ARef est de type A, par conséquent aRef.Intvalue est résolu à la compilation au moment de la compilation.
Il n'y a pas de polymorphisme pour les champs en Java.
La décision Variables
se produit à la compilation, donc toujours Les variables de la classe de base (les variables héritées de l’enfant) seront utilisées.
Ainsi, chaque fois que survient la surchauffe, rappelez-vous toujours
1) Les variables de la classe de base seront accessibles.
2) Les méthodes Sub Class (les méthodes substituées si le remplacement est passé, les autres méthodes héritées telles qu'elles sont du parent) seront appelées.
Concept OverRiding en Java Les fonctions remplaceront en fonction du type d'objet et les variables accédées en fonction du type de référence.
Par exemple:
Parent parent=new Child();
parent.behaviour();
Ici parent
est une référence de classe Parent mais contient un objet de classe enfant, c'est pourquoi la fonction de classe enfant sera appelée dans ce cas.
Child child=new Child();
child.behaviour();
Ici child
contient un objet de classe enfant, ainsi la fonction de classe enfant sera appelée.
Parent parent=new Parent();
parent.behaviour();
Ici, parent
contient l'objet de la classe parent, ainsi la fonction de la classe parent sera appelée.
Lorsque vous essayez d'accéder à la variable, cela dépend de l'objet de type référence, pas du type d'objet.
Par exemple:
Parent parent=new Child();
System.out.println(parent.state);
Le type de référence est Parent. La variable de classe Parent est accessible, et non la variable de classe Child.
Child child=new Child();
System.out.println(child.state);
Ici, le type de référence est Child. La variable de classe Child n'est donc pas utilisée, mais la variable de classe Parent.
Parent parent=new Parent();
System.out.println(parent.state);
Ici, le type de référence est Parent. La variable de classe Parent est accessible.
A partir de JLS Java SE 7 Edition §15.11.1:
Ce manque de recherche dynamique pour les accès sur le terrain permet aux programmes d'être exécutés efficacement avec des implémentations simples. La capacité de liaison et de substitution tardives est disponible, mais uniquement lorsque des méthodes d'instance sont utilisées.
Les réponses d'Oliver Charlesworth et de Marko Topolnik sont correctes, je souhaiterais en dire un peu plus sur la partie {pourquoi} de la question:
En Java membres de la classe are accédé en fonction du type de la référence et non du type de l'objet réel. Pour la même raison, si vous aviez une someOtherMethodInB()
dans la classe B
, vous ne pourrez pas y accéder à partir de aRef
après l'exécution de aRef = b
. Les identifiants (c'est-à-dire les noms de classe, de variable, etc.) sont résolus au moment de la compilation et le compilateur s'appuie donc sur le type de référence pour le faire.
Dans votre exemple, lorsque vous exécutez System.out.println(aRef.intVal);
, il affiche la valeur intVal
définie dans A
car il s'agit du type de référence que vous utilisez pour y accéder. Le compilateur voit que aRef
est de type A
et que c'est la intVal
à laquelle il aura accès. N'oubliez pas que vous avez les deux _ champs dans les instances de B
. JLS a également un exemple similaire au vôtre, "15.11.1-1. Liaison statique pour l'accès aux champs" si vous voulez y jeter un coup d'œil.
Mais pourquoi les méthodes se comportent-elles différemment? La réponse est que pour les méthodes, Java utilise late binding. Cela signifie qu’au moment de la compilation, il trouve la méthode la plus appropriée pour recherche pendant l’exécution. La recherche implique le cas où la méthode est remplacée dans une classe.
Ceci est appelé masquage de variable . Lorsque vous affectez aRef = b;
, aRef
a deux intVal, 1 est nommé intVal
, un autre est masqué sous A.intVal
(voir la capture d'écran du débogueur), car votre variable est de type class A
, même lorsque vous imprimez simplement intVal
. Java récupère intelligemment A.intVal
.
Réponse 1 : Un moyen d'accéder à la variable intVal
de la classe enfant est System.out.println((B)aRef.intVal);
Réponse 2 : Une autre façon de le faire est Java Reflection car lorsque vous utilisez une réflexion, Java ne peut pas lire intelligemment le code A.intVal
caché en fonction du type de classe, il doit saisir le nom de la variable sous forme de chaîne -
import Java.lang.reflect.Field;
class A{
public int intVal = 1;
public void identifyClass()
{
System.out.println("I am class A");
}
}
class B extends A
{
public int intVal = 2;
public void identifyClass()
{
System.out.println("I am class B");
}
}
public class Main
{
public static void main(String [] args) throws Exception
{
A a = new A();
B b = new B();
A aRef;
aRef = a;
System.out.println(aRef.intVal);
aRef.identifyClass();
aRef = b;
Field xField = aRef.getClass().getField("intVal");
System.out.println(xField.get(aRef));
aRef.identifyClass();
}
}
Sortie -
1
I am class A
2
I am class B
J'espère que cela peut aider
public class B extends A {
// public int intVal = 2;
public B() {
super();
super.intVal = 2;
}
public void identifyClass() {
System.out.println("I am class B");
}
}
Selon les spécifications Java, les variables d'instance ne sont pas écrasées d'une super classe par une sous-classe lorsqu'elle est étendue.
Par conséquent, la variable de la sous-classe uniquement peut être vue comme une variable partageant le même nom.
De même, lorsque le constructeur de A est appelé lors de la création de l'instance de B, la variable (intVal) est initialisée et donc la sortie.
J'espère que tu as la réponse. Sinon, vous pouvez essayer de voir en mode débogage. la sous-classe B a accès à l'intVal. Ils ne sont pas polymorphes et ne sont donc pas surchargés.
Si vous utilisez la référence de B, vous obtiendrez son intVal. Si vous utilisez la référence de A, vous obtiendrez son intVal. C'est si simple.