Considérez l'exemple de code suivant
class MyClass {
public String var = "base";
public void printVar() {
System.out.println(var);
}
}
class MyDerivedClass extends MyClass {
public String var = "derived";
public void printVar() {
System.out.println(var);
}
}
public class Binding {
public static void main(String[] args) {
MyClass base = new MyClass();
MyClass derived = new MyDerivedClass();
System.out.println(base.var);
System.out.println(derived.var);
base.printVar();
derived.printVar();
}
}
il donne la sortie suivante
base
base
base
derived
Les appels de méthode sont résolus au moment de l'exécution et la méthode remplacée correcte est appelée, comme prévu.
L'accès aux variables est plutôt résolu au moment de la compilation comme je l'ai appris plus tard. Je m'attendais à une sortie
base
derived
base
derived
car dans la classe dérivée, la redéfinition de var
fait de l'ombre à celle de la classe de base.
Pourquoi la liaison des variables se produit-elle au moment de la compilation et non au moment de l'exécution? Est-ce uniquement pour des raisons de performances?
La raison est expliquée dans la spécification de langage Java dans un exemple de Section 15.11 , citée ci-dessous:
...
La dernière ligne montre qu'en effet, le champ auquel on accède ne dépend pas de la classe d'exécution de l'objet référencé; même si
s
contient une référence à un objet de classeT
, l'expressions.x
fait référence au champx
de la classeS
, car le type de l'expressions
estS
. Les objets de la classe T contiennent deux champs nommésx
, un pour la classeT
et un pour sa superclasseS
.Ce manque de recherche dynamique pour les accès aux champs permet aux programmes d'être exécutés efficacement avec des implémentations simples. La puissance de la liaison et du remplacement tardifs est disponible, mais uniquement lorsque des méthodes d'instance sont utilisées...
Alors oui, la performance est une raison. La spécification de la façon dont l'expression d'accès au champ est évaluée est la suivante:
Si le champ n'est pas
static
:...
- Si le champ est un
final
non vide, le résultat est la valeur du champ membre nommé de typeT
trouvé dans l'objet référencé par la valeur de Primary.
où Primaire dans votre cas fait référence à la variable derived
qui est de type MyClass
.
Une autre raison, comme l'a suggéré @Clashsoft, est que dans les sous-classes, les champs ne sont pas remplacés, ils sont cachés. Il est donc logique d'autoriser l'accès aux champs en fonction du type déclaré ou à l'aide d'un transtypage. Cela est également vrai pour les méthodes statiques. C'est pourquoi le champ est déterminé en fonction du type déclaré. Contrairement à la substitution par les méthodes d'instance, cela dépend du type réel. La citation JLS ci-dessus mentionne en effet implicitement cette raison:
La puissance de la liaison et du remplacement tardifs est disponible, mais uniquement lorsque des méthodes d'instance sont utilisées.
Bien que vous ayez raison sur les performances, il existe une autre raison pour laquelle les champs ne sont pas distribués dynamiquement: vous ne pourrez pas accéder à MyClass.var
du tout si vous aviez une instance MyDerivedClass
.
En règle générale, je ne connais aucun langage de type statique ayant en fait une résolution variable dynamique. Mais si vous en avez vraiment besoin, vous pouvez faire des getters ou des méthodes d'accesseur (ce qui devrait être fait dans la plupart des cas pour éviter les champs public
, de toute façon):
class MyClass
{
private String var = "base";
public String getVar() // or simply 'var()'
{
return this.var;
}
}
class MyDerivedClass extends MyClass {
private String var = "derived";
@Override
public String getVar() {
return this.var;
}
}
Le comportement polymorphe du langage Java fonctionne avec les méthodes et non avec les variables membres: ils ont conçu le langage pour lier les variables membres au moment de la compilation.
En Java, c'est par conception. Parce que la configuration des champs à résoudre dynamiquement rendrait les choses un peu plus lentes. Et en réalité, il n'y a aucune raison de le faire. Depuis, vous pouvez créer vos champs dans n'importe quelle classe privé et y accéder avec méthodes qui sont résolus dynamiquement.
Ainsi, les champs sont mieux résolus à au moment de la compilation à la place :)