I'm training for a Java exam, and I've come across something I don't understand in last year subject. Here is the code
class Mother {
int var = 2;
int getVar() {
return var;
}
}
class Daughter extends Mother {
int var = 1;
int getVar() {
return var;
}
public static void main(String[]开发者_运维知识库 args) {
Mother m = new Mother();
System.out.println(m.var);
System.out.println(m.getVar());
m = new Daughter();
System.out.println(m.var);
System.out.println(m.getVar());
}
}
The question is "what is the output of this program?". I would have go with 2 2 1 1, but when compiling and running this piece of code, I get 2 2 2 1.
Anyone can explain me why ?
Thanks for reading !
The method call m.getVar()
is a virtual method call. The second time you call it, it's dynamically dispatched to the derived Daughter.getVar()
, which does what you expect (accesses Daugther.var
and returns that).
There is no such virtual dispatch mechanism for member fields. So m.var
always refers to Mother.var
, i.e. the base class's version of that variable.
The Daughter
class can be seen as having two different var
member: the one from Mother
and its own. Its own member "hides" the one in Mother
, but can be accessed from within the Daughter
class by using super.var
.
The official spec for this is in section 8.3 Field Declarations of the JLS. Quote:
If the class declares a field with a certain name, then the declaration of that field is said to hide any and all accessible declarations of fields with the same name in superclasses, and superinterfaces of the class. The field declaration also shadows (§6.3.1) declarations of any accessible fields in enclosing classes or interfaces, and any local variables, formal method parameters, and exception handler parameters with the same name in any enclosing blocks.
Note that it can get pretty interesting (emphasis added):
If a field declaration hides the declaration of another field, the two fields need not have the same type.
And:
There might be several paths by which the same field declaration might be inherited from an interface. In such a situation, the field is considered to be inherited only once, and it may be referred to by its simple name without ambiguity.
So that paragraph is well worth reading :-)
Focus on these lines:
Mother m;
m = new Daughter();
System.out.println(m.var);
System.out.println(m.getVar());
You are constructing a Daughter object, but you are treating it like it's base class Mother. So when you access m.var you are accessing the base class variable var. Meanwhile when you call a method, even if you are referring to the base class reference, the overrided method is called. It's a different behavior for methods and fields.. Fields reference cannot be overrided.
Methods can be overridden however fields can only be hidden. The difference is that a non-static method uses the type of the object referenced, a field takes the type of the reference. You see a similar thing with static methods which only be hidden where the class of the "reference" and the object (if provided) is ignored.
For your interest, try giving the fields different types. ;)
You can also try
System.out.println(((Mother)m).var); // uses var in Mother
System.out.println(((Daughter)m).var); // uses var in Daughter
m = new Daughter();
Though you have created a daughter
object, you are referring that object with Mother m
reference. So, any call using m
will call the Mother class members, not daughter's
I ran this in Eclipse and checked the values with debugger, the debugger actually shows the local m
-variable having TWO different var -members after the m = new Daugher()
-line with values 2 and 1. m.var
seems to resolve to the one in Mother, and the m.getVar() calls the getVar in Daughter (as expected).
However, when I change the main-method to look like this:
Mother m = new Mother();
System.out.println(m.var);
System.out.println(m.getVar());
Daughter d = new Daughter();
System.out.println(d.var);
System.out.println(d.getVar());
It actually outputs 2, 2, 1, 1, so it would seem that the declaration of the variable affects which class's var
is used.
I read the answers and non of them (so far) gave good reason why in Object oriented language as Java is, this should be the case. I will try to explain.
Suppose that you have function that takes Mother as arg:
void foo(Mother m) {
print(m.var);
}
This function (actually the compiler) has no idea if you will call it with Mother
, Daughter
or with another Dauther2
that doesn't even have var
variable declared. Because of that, when the reference is of type Mother, reference to a member variable must be linked (by the compiler) to the Mother
's member. The similar applies to function too, so the functions are linked to Mother's declaration of getVar()
, but not to Mother
's implementation of getVar()
So, member variables are always linked (by the compiler) based on the reference. Another way to explain it: If you remove Mother
's var (and make Mother's getVar()
compilable), your second m.var
(when m refers to Daughter) won't compile even Daughter
has member var.
I hope i was clear.
精彩评论