4 minutes
2: Inheritance in Java
Inheritance in Java – Sharing and Surprising Behaviors
Introduction
Inheritance is often touted as the heart of OOP: a way to share code, model hierarchies, and reuse behavior. In Java, inheritance brings its own set of rules and quirks, especially when you’re guiding students through it for the first time. Let’s dive into Java’s inheritance model, explore some teaching moments, and uncover the surprises that only show up when you’re really using it.
1. The Basics: extends
and the Class Hierarchy
Every Java class (except Object
) extends another class:
public class Animal {
public void eat() {
System.out.println("Nom nom");
}
}
public class Dog extends Animal {
public void bark() {
System.out.println("Woof!");
}
}
Dog
inheritseat()
fromAnimal
.- The root of all classes is
java.lang.Object
, so even if you don’t writeextends
, your class is silently extendingObject
.
public class MyClass { }
// is actually:
public class MyClass extends Object { }
Teaching Tip:
Show students how every class has methods like toString()
, hashCode()
, and equals()
because they come from Object
.
2. Constructor Chaining and super()
When you new
up a subclass, Java ensures the superclass is initialized first:
public class Person {
public Person(String name) {
System.out.println("Person created: " + name);
}
}
public class Employee extends Person {
public Employee(String name, String id) {
super(name); // mandatory call to super
System.out.println("Employee ID: " + id);
}
}
- If you don’t call
super(...)
explicitly, Java inserts a no-argsuper()
, but only if the superclass has a no-arg constructor. - Missing or mismatched constructors lead to confusing errors.
Teaching Tip:
Demonstrate the compile-time error when a superclass lacks a matching no-arg constructor, then show how adding super(args)
resolves it.
3. Method Overriding and Polymorphism
Subclasses can override methods to provide specialized behavior:
@Override
public void eat() {
System.out.println("Crunch crunch");
}
With a reference of the parent type, overridden methods still dispatch to the subclass:
Animal a = new Dog();
a.eat(); // Crunch crunch
Quirk:
Java disallows overriding methods marked final
. And private
methods are invisible to subclasses, not overridden but hidden.
4. Single Inheritance vs. Multiple Interfaces
Java classes can extend only one parent class (single inheritance), but they can implement multiple interfaces:
public interface Swimmer { void swim(); }
public interface Runner { void run(); }
public class Triathlete implements Swimmer, Runner {
public void swim() { }
public void run() { }
}
Java 8 Default Methods and the Diamond
Since Java 8, interfaces can have default methods:
public interface Flyer {
default void move() { System.out.println("Flap flap"); }
}
public interface Walker {
default void move() { System.out.println("Step step"); }
}
public class Duck implements Flyer, Walker {
// Must disambiguate:
public void move() {
Flyer.super.move(); // or Walker.super.move();
}
}
- If two interfaces provide the same default method, the implementing class must override it, otherwise it won’t compile.
Teaching Tip:
Walk through a “diamond inheritance” scenario to show why Java forces explicit disambiguation.
5. Visibility and the protected
Gotcha
public
: visible everywhere.private
: visible only in the declaring class.protected
: visible in the same package and in subclasses (even if in different packages).- (default/package-private): visible only within the same package.
Quirk:
Students often assume protected
means “only subclasses,” but it’s broader: package-level code without subclass ties can also see it if they’re in the same package.
6. final
Classes and Methods
- A
final
class cannot be subclassed (e.g.,String
). - A
final
method cannot be overridden. - Useful when you want to lock down behavior or prevent unsafe subclassing.
7. Covariant Return Types
Since Java 5, overridden methods can return a subtype of the original return type:
class Fruit {}
class Apple extends Fruit {}
class Orchard {
Fruit pick() { return new Fruit(); }
}
class AppleOrchard extends Orchard {
@Override
Apple pick() { return new Apple(); } // legal
}
Teaching Tip:
Explain how covariant returns improve API usability without compromising type safety.
Key Takeaways
- Java’s single inheritance model is complemented by multiple interfaces.
- Constructor chaining always travels up to
Object
. - Default methods introduced interface inheritance quirks.
- Visibility rules (especially
protected
) can surprise new Java developers. final
and covariant returns fine-tune how and whether subclasses can change behavior.
In the next post, we’ll tackle polymorphism in action, dynamic dispatch, abstract classes, and when to choose interfaces vs. abstract base classes. Stay tuned!