First: As a general rule, if a class defines a field that a subclass can access, the subclass should not redefine the field. It's just a Really Bad Idea. Primarily what you're seeing is there to make private fields work properly. Redefining non-private fields in a subclass is asking for a world of hurt. (Of course, if Joe writes Mammal
and Mary writes Zebra
and at some point Joe adds a field to Mammal
that happens to conflict with one that Mary used in Zebra
, there's nothing Mary can do about that. Which is one reason for making all fields private.)
But why does sub class object stores values of super class variables as well, even in case of hiding.
The key here is to remember that fields are not polymorphic, just methods. So there have to be two name
fields in the object (one from Mammal
and one from Zebra
), because code using a Mammal
-typed reference needs to see the Mammal
name
, whereas code using the Zebra
-typed reference needs to see the Zebra
name
.
That's why your code shows "furry bray" and then "stripes bray". You get "furry bray" via m
because accessing name
on m
(a Mammal
-typed variable) accesses Mammal
's name
(not polymorphic), giving you "furry". But then you call the method makeNoise
using m
and get back "bray"
, because the method that gets called is the one on Zebra
(polymorphic). Then you do it again with z
(a Zebra
-typed reference) and see "stripes bray" because z
accesses Zebra
's name
, not Mammal
's.
The next question you might have is: If we change makeNoise
in both classes to:
String makeNoise() {
return this.name;
}
why does ZooKeeper
's code
Mammal m = new Zebra();
System.out.println(m.name + m.makeNoise());
Zebra z = new Zebra();
System.out.println(z.name + z.makeNoise());
give us "furry stripes" from m
and stripes stripes
from z
?
It's the same reason, just a different presentation of it. m.name
accesses name
from a Mammal
-typed reference, and so sees Mammal
's name
(not polymorphic). m.makeNoise
calls Zebra
's makeNoise
method (polymorphic), and inside Zebra
's makeNoise
, this
has the type Zebra
even though we called it from a Mammal
-typed m
(and so this.name
uses Zebra
's name
). The fact that Zebra's makeNoise
is used there, and the fact that this
within Zebra
code is typed Zebra
are both key to polymorphism in Java.
Let's take it further: What if Zebra
doesn't define makeNoise
at all?
class Mammal {
String name = "furry ";
String makeNoise() {
return this.name;
}
}
class Zebra extends Mammal {
String name = "stripes ";
}
Now we get "furry furry" from m
and "stripes furry" from z
. And the reason for it is the same as above: The type of the reference determines which field is used, and in Mammal
code (makeNoise
), this
has the type Mammal
. So even though we called makeNoise
using z
, since Zebra
has no makeNoise
, Mammal
's is called, so the reference that looks up name
has the type Mammal
.
Is there any use of this?
It's crucial to classes working properly, particularly in the case of private fields. Mammal
code doesn't have to worry about a subclass coming along and redefining its fields. You could have a 10-deep class hierarchy, with each class defining its own name
, and that's fine, each level's code works with the name
it defines.