Inheritance

Nathan Sprague

Creature Class

We are developing a super-fun text-based video game:

public class Creature {

    private int xPos;
    private int yPos;
    private int health;

    public Creature(int xPos, int yPos, int health) {
        this.xPos = xPos;
        this.yPos = yPos;
        this.health = health;
    }

    public String makeFace() {
        return "OvO";
    }
    
    public int getXPos() {
        return xPos;
    }

    public int getYPos() {
        return yPos;
    }
    
    public void setXPos(int xPos) {
        this.xPos = xPos;
    }

    public void setYPos(int yPos) {
        this.yPos = yPos;
    }

    public int getHealth() {
        return health;
    }

    public void setHealth(int health) {
        this.health = health;
    }
}

Monster Class

Of course… the game will need several different creature types. Let’s add a monster class with a different appearance:

What’s wrong with this design so far?

What is the solution?

Superclass/Subclass UML

  • A Monster “is-a” Creature.
    • Monster inherits all Creature attributes.
    • Monster can have additional methods and variables.
    • Monster can override existing methods.
  • Warning! private fields are really private.
    • E.g. xPos is only accessible from the Creature class.
    • A Monster has an xPos, but it can’t be accessed directly by the Monster class.

The Worst Thing Possible: Shadowing Instance Variables

public class Monster extends Creature { // NOTICE THE "extends" KEYWORD!
    private int xPos;
    private int yPos;
    private int health;
    private boolean angry;

    public Monster(int xPos, int yPos, int health) {
        super(xPos, yPos, health); // Notice the call to superclass constructor
        this.xPos = xPos;
        this.yPos = yPos;
        this.health = health;
        this.angry = false;
    }
    
    public String makeFace() {
        if (angry) {
            return ":(>)" + " " + health;
        } else {
            return ":)" + " " + health;
        }
    }   
    
    public void setHealth(int health) {
        angry = health < 3;
        this.health = health;
    }
}

What will be printed by this code snippet:

       Monster monster = new Monster(50, 50, 10);
        
       System.out.println(monster.getHealth());
       System.out.println(monster.makeFace());
       
       monster.setHealth(1);
        
       System.out.println(monster.getHealth());
       System.out.println(monster.makeFace())
10
:) 10
10
:(>) 1
10
:) 10
10
:) 10
10
:) 10
1
:(>) 1
10
:) 10
1
:) 10

Monster Code (Without Shadowing)

public class Monster extends Creature {

    private boolean angry;

    public Monster(int xPos, int yPos, int health) {
        super(xPos, yPos, health);
        this.angry = false;
    }
    
    public String makeFace() {
        if (angry) {
            return ":(>)";
        } else {
            return ":)";
        }
    }


    public void setHealth(int health) {
      //...
    }

Socrative Quiz: setHealth Method

We want the Monster to become angry when its health falls below 3.

Which implementation works?

    public void setHealth(int health)
    {
        angry = health < 3;
        this.health = health;
    }
    public void setHealth(int health)
        angry = health < 3;
        setHealth(health);
    }
    public void setHealth(int health)
        angry = health < 3;
        super.setHealth(health);
    }
    public void setHealth(int health)
        angry = health < 3;
        super.health = health;
    }

Correct Monster Class…

public class Monster extends Creature
{

    private boolean angry;

    public Monster(int xPos, int yPos, int health) {
        super(xPos, yPos, health);
        this.angry = false;
    }
    
    public String makeFace() {
        if (angry) {
            return ":(>)";
        } else {
            return ":)";
        }
    }


    public void setHealth(int health) {
        angry = health < 3;
        super.setHealth(health); // health instance variable is private!
    }

Alternate Monster Class…

public class Monster extends Creature {

    private boolean angry;

    public Monster(int xPos, int yPos, int health) {
        super(xPos, yPos, health);
        this.angry = false;
    }
    
    public String makeFace() {
        if (angry) {
            return ":(>)";
        } else {
            return ":)";
        }
    }

    public void setHealth(String health) {
        super.setHealth(Integer.parseInt(health));
        angry = getHealth() < 3;
    }

What will be printed by the following code snippet?

       Monster  monster  = new Monster(50, 50, 10);
       
       monster.setHealth(1);
       System.out.println(monster.getHealth());
       System.out.println(monster.makeFace());
       
       monster.setHealth("2");
       System.out.println(monster.getHealth());
       System.out.println(monster.makeFace())
1
:(>)
2
:(>)
10
:)
2
:(>)
1
:)
1
:)
1
:)
2
:(>)

@Override

The problem here is that the author intended to override the setHealth method of the superclass, but overloaded it instead.

This type of error can be avoided by using the @Override annotation. It informs the compiler that you intend a method to override a method defined in the superclass.

This wouldn’t compile, because Creature has no method with this signature.

    @Override
    public void setHealth(String health) {
        super.setHealth(Integer.parseInt(health));
        angry = getHealth() < 3;
    }

One More Quiz…

Let’s add a toString method to Creature:

public class Creature
{
    // Other code not shown.
    
    public String toString() {
        return makeFace() + " " + xPos + " " + yPos;
    }
}

What will be printed when this code snippet executes?

Creature c1 = new Creature(20, 30, 10);
Monster m1 = new Monster(15, 17, 8);

System.out.println(c1);
System.out.println(m1);
OvO 20 30
OvO 15 17
OvO 20 30
:) 15 17

Answer

B. Calling makeFace on an object of type Monster will always execute the Monster version of the method.