Reputation: 136277
I have created the following puzzle for inheritance in Java:
Animal.java
public class Animal {
private String sound;
public void roar() {
System.out.println(sound);
}
public void setSound(String sound) {
this.sound = sound;
}
}
Tiger.java
public class Tiger extends Animal {
public String sound;
public Tiger() {
sound = "ROAR";
}
}
Jungle.java
public class Jungle {
public static void main(String[] args) {
Tiger diego = new Tiger();
diego.roar();
diego.sound = "Hust hust";
diego.roar();
diego.setSound("bla");
diego.roar();
System.out.println(diego.sound);
}
}
Output:
null
null
bla
Hust hust
I guess this weird behaviour is taking place, because sound
in Animal is private while sound
in Tiger is public. But can you explain (and tell me the relevant parts of the JLS) why this happens?
Upvotes: 8
Views: 1645
Reputation: 66243
As other have already noted: Fields are not subject to polymorphism.
My new twist to this is: Access to fields is decided statically at compile time, not dynamically at runtime. So here
Tiger diego = new Tiger();
diego.sound = "Hust hust";
the variable diego
has the static type Tiger
. So the compiler will generate an access to Tiger.sound
. But in contrast (if Animal.sound
would not be private
) :
Animal diego = new Tiger();
diego.sound = "Hust hust";
the compiler will generate an access to Animal.sound
. This can be also forced by casting:
Tiger diego = new Tiger();
((Animal)diego).sound = "Hust hust";
With this in mind you can go through your puzzle and for each access to any sound
field you can tell the static type either of the implicit this
or of diego
at that point. Then you also know which of both fields is actually accessed.
Upvotes: 2
Reputation: 14247
Change the Tiger
class to:
public class Tiger extends Animal {
public Tiger() {
setSound("ROAR");
}
}
The problem is that the roar()
method defined in Animal
uses the member private field sound
defined in the very same Animal
class.
The sound
from Animal
isn't visible to the Tiger
class, because it's private. So you declared a new sound
field for the Tiger
subclass, but that didn't override the original one from Animal
. The Animal
class still uses it's own version of sound
, because it's the only version it sees. Unlike methods, field can't be overriden.
One solution is to use getter/setter methods declared in the base class (Animal
) for all access to properties, even from the subclasses.
Another possible solution would be using abstract methods and polymorphism:
You don't implement sound method in the Animal base class, you just declare an abstract method and force subclasses to provide their own implementations:
public abstract class Animal {
public void roar() {
System.out.println(sound());
}
public abstract String sound();
}
public class Tiger extends Animal {
public String sound() {
return "ROAR";
}
}
public class Dog extends Animal {
public String sound() {
return "HOOF HOOF";
}
}
Even though there is no implementation (no body with code) of the sound()
method in Animal, it is still possible to call that method from other methods of this class, such as roar()
.
Of course, this approach makes you unable to change the sound of an existing animal object (there is no setter), making animals immutable, which might seem inconvenient at first, but if you think about it for a while, you might find out that in many cases, you do not actually need to change state of the objects in this manner.
Using immutable objects is actually convenient, because the code is more simple and secure, because you don't have to think about all the possible states that can possible occur during the execution of the program.
Upvotes: 0
Reputation: 3128
You have to recognize the Animal.sound
is not the same field as Tiger.sound
. You in fact have two different fields, that can have two different values, and are set in two different ways.
Animal.setSound()
updates the value of Animal.sound
, does not update the value of Tiger.sound
.
diego.sound = "Hust hust"
updates the value of Tiger.sound
, not the value of Animal.sound
.
See the section What you can do in a subclass in the Inheritance Turorial.
Upvotes: 1
Reputation: 66637
Fields are not polymorphic, methods are polymorphic.
diego.roar();
calls roar()
method in Animal
and prints sound
from Animal
.
diego.sound = "Hust hust";
Sets sound value in Tiger
class sound
variable
diego.roar();
returns null; because prints sound from Animal, which is still null. Above sound assignment reflects on Tiger class variable, not Animal class.
diego.setSound("bla");
sets Animal
sound to bla
diego.roar();
prints bla
because setSound update sound variable of Animal class with bla
.
System.out.println(diego.sound);
prints Hust hust
due to the fact that diego is of type Tiger
and you have accessed field sound of Tiger
and fields are not polymorphic.
Please refer java language specification 8.3 for more details.
Upvotes: 10
Reputation: 47267
You can override functions in Java, not variables.
Remove the line public String sound;
from Tiger.java
and either:
String sound
as protected
or public
in Animal.java
, or setSound()
function for Animal.java
for controlled access to member variables (i.e. sound
)For a fuller explanation, see Jon Skeet's excellent answer to an almost identical problem yesterday.
Upvotes: 2
Reputation: 444
system out statements (1) and (2) are referring to the instance variable sound of superclass which is not inherited as instance/class variables are not inherited in java and you havent set super variable. (3), the super variable is set via calling the inherited method.(4) is set when you did a direct assignment.
Upvotes: 0