Martin Thoma
Martin Thoma

Reputation: 136277

Java Inheritance Puzzle

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

Answers (6)

A.H.
A.H.

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

Natix
Natix

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

romacafe
romacafe

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

kosa
kosa

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

sampson-chen
sampson-chen

Reputation: 47267

You can override functions in Java, not variables.

Remove the line public String sound; from Tiger.java

and either:

  • Declare String sound as protected or public in Animal.java, or
  • Define a 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

CodeDreamer
CodeDreamer

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

Related Questions