Skip to main content

Section 8.7 Overriding Methods

Inheritance allows you to take a method from your superclass and replace it with your own version in the subclass. This is called method overriding. To override a method, define a method in your subclass with exactly the same signature as the superclass method.
Always use the @Override annotation when overriding methods. While optional, this annotation helps the compiler catch mistakes if you accidentally change the method’s name or parameters.
public class Monster extends Entity {
    private boolean isUndead;

    public Monster(String name, int initialHealth, boolean isUndead) {
        super(name, initialHealth);
        this.isUndead = isUndead;
    }

    @Override
    public void takeDamage(int amount) {
        // Undead monsters take half damage
        if (isUndead) {
            int reducedAmount = amount / 2;
            System.out.println(getName() + " resists damage due to undead nature!");
            super.takeDamage(reducedAmount); // Call the parent version with reduced damage
        } else {
            // Call the original implementation for non-undead monsters
            super.takeDamage(amount);
        }
    }

    @Override
    public void heal(int amount) {
        // Undead monsters are damaged by healing
        if (isUndead) {
            System.out.println("Healing harms the undead " + getName() + "!");
            takeDamage(amount);
        } else {
            super.heal(amount);
        }
    }
}
Here, Monster inherits and overrides both the takeDamage and heal methods to implement specialized behavior for undead monsters: they take half damage but are harmed by healing effects.

Note 8.7.1. When to Override vs. When to Avoid Overriding.

Method overriding is most appropriate when:
  • The subclass needs to modify or extend the superclass behavior while maintaining the same method contract
  • The override still fulfills the original purpose of the method without breaking code that might use this object through its parent type
  • The behavior change is a natural specialization that users of the class would expect
Avoid overriding when:
  • The override would drastically change the expected behavior, violating the superclass contract
  • The override would disable or "turn off" functionality expected from the superclass
  • You find yourself overriding many methods of the superclass, indicating the inheritance relationship might not be appropriate

Insight 8.7.2. @Override Best Practices.

Always use the @Override annotation when overriding methods in subclasses. It costs nothing to add but provides two significant benefits:
  • The compiler verifies that you’re actually overriding a superclass method
  • It makes your code more readable by explicitly indicating overridden methods
Without @Override, common mistakes like typos in method names, parameter type mismatches, or return type differences will create a new method instead of overriding the superclass method, leading to subtle bugs.
For instance, consider this common mistake:
// A bug without @Override
public class Monster extends Entity {
    // ...

    // Typo in method name - note the "i" in "takeDimage"
    public void takeDimage(int amount) {
        // This doesn't override Entity.takeDamage()
        // It creates a completely new method!
        health -= (amount / 2);
    }
}

// Usage elsewhere in code
Monster zombie = new Monster("Zombie", 100, true);
zombie.takeDamage(20); // Will call Entity.takeDamage, not the Monster version!
In this example, the typo creates a new method rather than overriding the existing one. When takeDamage is called on a Monster, it will use the original Entity implementation, not the intended specialized version. With @Override, the compiler would catch this error immediately with a message like: "Method does not override method from its superclass".

Subsection 8.7.1 Overriding Object Methods

As we saw in the previous chapter, every Java class implicitly inherits from Object. This means you can override Object’s methods to customize behavior that makes sense for your specific classes. Three of the most commonly overridden methods are toString() , equals(), and hashCode().
Let’s look at examples of overriding these methods and understand why doing so is important:

Subsubsection 8.7.1.1 Overriding toString()

The toString() method provides a string representation of an object. By default, it returns a string containing the class name followed by an @ symbol and the object’s hash code in hexadecimal format.
public class Player extends Entity {
    private int experiencePoints;
    private int level;

    // Constructor and other methods...

    @Override
    public String toString() {
        return "Player{name='" + getName() + "', health=" + getHealth() +
            ", level=" + level + ", xp=" + experiencePoints + "}";
    }
}
Overriding toString() has several benefits:
  • Makes debugging easier by providing meaningful object information
  • Improves logs and error messages
  • Simplifies debugging arrays and collections
With the override above, System.out.println(player) now produces readable output like: Player{name='Hero', health=100, level=1, xp=0}

Subsubsection 8.7.1.2 Overriding equals() and hashCode()

The equals() method determines whether two objects are equivalent. By default, it uses reference equality (==), checking if two variables refer to exactly the same object instance in memory.
The hashCode() method returns a numeric value that uniquely represents an object and is used in various operations that require efficient lookups.
Note 8.7.3. The equals/hashCode Contract.
There’s a critical rule in Java: if you override equals(), you must also override hashCode() to ensure consistent behavior. Two objects that are equal according to equals() must return the same hashCode() value.
public class Item {
    private String name;
    private int value;

    public Item(String name, int value) {
        this.name = name;
        this.value = value;
    }

    // Getters omitted for brevity

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;

        Item other = (Item) obj;
        return value == other.value && Objects.equals(name, other.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, value);
    }
}
With these overrides, two different Item objects with the same name and value will be considered equal:
Item sword1 = new Item("Sword", 50);
Item sword2 = new Item("Sword", 50);

// Without overriding equals, this would print "false"
// With our override, it prints "true"
System.out.println(sword1.equals(sword2));
If you override equals() but don’t override hashCode(), unexpected behavior can occur in certain situations where objects are expected to be uniquely identified. Here’s a real-world example of what can go wrong when you override equals() but not hashCode().
The reason for this is that many Java collections (such as HashSet and HashMap) rely on hashCode() to efficiently organize and retrieve objects. If two objects are considered equal according to equals(), they must have the same hashCode() to avoid unexpected behavior in these collections. However, for this course, we won’t be using hash-based collections, so you don’t need to worry about overriding hashCode(). That said, it’s a good practice to keep in mind for future Java programming, especially when working with data structures that optimize lookups.

Insight 8.7.4. Practical Benefits.

Overriding Object methods demonstrates a key inheritance principle: you can leverage the general structure provided by the superclass while customizing behavior for your specific needs. This pattern applies throughout Java development, whether customizing built-in classes or extending your own superclasses.
Check Your Understanding

Checkpoint 8.7.5.

What is method overriding in Java?
  • Providing a new implementation for an inherited method in a subclass.
  • Defining multiple methods with the same name but different parameters in the same class.
  • Using the final keyword to prevent method modifications.
  • Creating a method with the same name but a different return type in a subclass.

Checkpoint 8.7.6.

What must be true for a method to be properly overridden in Java?
  • The method must have the same name, parameters, and return type as the superclass method.
  • The method must have a different name but the same parameters.
  • The method must be static in both the superclass and subclass.
  • The method must have a different return type but the same name and parameters.

Checkpoint 8.7.7.

Can a subclass override a private method from its superclass?
  • Yes, but only if the subclass is in the same package.
  • No, because private methods are not inherited.
  • Yes, as long as the method is also declared private in the subclass.
  • No, unless the subclass uses the super keyword to call the method.
You have attempted of activities on this page.