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.
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.
// 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".
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().
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.
Subsubsection8.7.1.2Overriding 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.
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.
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.
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.