Inheritance — Junior¶
What? Inheritance is the mechanism by which one class (the subclass) extends another (the superclass), automatically gaining its fields and methods, and optionally overriding or adding to them. How? With the
extendskeyword. A class can extend exactly one other class. Every class that doesn't explicitly extend something extendsObject.
1. The simplest example¶
class Animal {
String name;
void breathe() { System.out.println(name + " breathes"); }
}
class Dog extends Animal {
void bark() { System.out.println(name + " barks"); }
}
Dog d = new Dog();
d.name = "Rex";
d.breathe(); // inherited from Animal
d.bark(); // declared in Dog
Dog is-a Animal. Every Dog has every member that Animal has, plus its own additions. The phrase "is-a" is the core test for inheritance: only inherit when "B is-a kind of A" makes sense in your domain.
2. What gets inherited¶
| Member | Inherited? |
|---|---|
| Public fields | Yes |
| Protected fields | Yes (same package or subclass) |
| Package-private | Yes within the same package |
| Private fields | No — exist in the parent but invisible |
| Public methods | Yes |
| Protected methods | Yes (same package or subclass) |
| Private methods | No |
| Constructors | Never inherited — but called via super(...) |
| Static members | Accessible via the subclass name (not "inherited" in the OO sense) |
A subclass cannot directly access its parent's private fields, but it can call public/protected getter/setter methods that operate on them.
3. The Object root¶
Every class in Java extends Object directly or transitively. So every object has these methods inherited from Object:
equals(Object)hashCode()toString()getClass()wait(),notify(),notifyAll()(legacy concurrency)clone()(protected; rarely used)finalize()(deprecated; do not override)
This is why you can call myDog.toString() even though Dog didn't declare it — it inherits the version from Object.
4. Constructors and inheritance¶
A subclass constructor must call a parent constructor first. If you don't write one, the compiler inserts an implicit super():
class Animal {
Animal() { System.out.println("Animal ctor"); }
}
class Dog extends Animal {
Dog() { // implicit super() here
System.out.println("Dog ctor");
}
}
new Dog();
// Animal ctor
// Dog ctor
If Animal only has constructors that take arguments, you must call super(...) explicitly:
class Animal {
Animal(String name) { /* ... */ }
}
class Dog extends Animal {
Dog(String name) {
super(name); // required — Animal has no no-arg ctor
}
}
5. Method overriding¶
A subclass can replace an inherited method's implementation by overriding it:
class Animal {
void speak() { System.out.println("..."); }
}
class Dog extends Animal {
@Override
void speak() { System.out.println("Woof"); }
}
class Cat extends Animal {
@Override
void speak() { System.out.println("Meow"); }
}
Animal a = new Dog();
a.speak(); // Woof — runtime decides based on actual object type
The @Override annotation tells the compiler "I intend to override; check me." If you misspell the name or change the signature, the compiler errors. Always use @Override.
6. The super keyword¶
super refers to the parent class. Two main uses:
Call parent's method from inside an override:
class Dog extends Animal {
@Override
void speak() {
super.speak(); // call Animal.speak()
System.out.println("then woof");
}
}
Call parent's constructor from inside subclass constructor:
7. Polymorphism: a reference to the parent type can hold any subclass¶
This is the foundation of polymorphism. A method that takes Animal can accept any subclass. A list List<Animal> can hold dogs and cats. Method calls dispatch to the actual runtime type:
List<Animal> zoo = List.of(new Dog(), new Cat(), new Animal());
for (Animal a : zoo) a.speak(); // Woof / Meow / ...
This is dynamic dispatch — handled by the JVM. We dive into the mechanism in senior.md and the dedicated Static vs Dynamic Binding topic.
8. The final modifier¶
You can prevent inheritance or overriding:
finalclass: cannot be extended.String,Integer, etc. are final.finalmethod: cannot be overridden by subclasses.finalfield: cannot be reassigned after initialization.
final class Money { /* nothing extends Money */ }
class Account {
final void close() { /* subclasses can't override */ }
}
Use final when extension would break invariants (security-sensitive, immutability-sensitive types).
9. Casting between types in a hierarchy¶
Upcast (always safe, often implicit):
Downcast (manual, can fail at runtime):
Java 16+ has pattern matching for instanceof:
This is safer than manual casting and reads better.
10. Single inheritance, multiple interfaces¶
A class extends at most one other class but can implement many interfaces:
Interfaces are covered in their own topic. The key lesson here: Java forbids multiple class inheritance to avoid the "diamond problem" (which parent's method should I inherit?). Interfaces with default methods solve it via explicit conflict resolution.
11. Composition vs inheritance¶
"Favor composition over inheritance" — Effective Java, Item 18.
Inheritance is tightly coupled: a subclass depends on its parent's implementation details. If the parent changes, subclasses break. Composition (holding a member and forwarding to it) is loosely coupled.
Inheritance:
class Stack<E> extends ArrayList<E> { // BAD — Stack inherits all List methods
public void push(E e) { add(e); }
}
Stack now leaks every ArrayList method (add, set, remove(int)) — users can corrupt the stack invariants.
Composition:
class Stack<E> {
private final ArrayList<E> data = new ArrayList<>();
public void push(E e) { data.add(e); }
public E pop() { return data.remove(data.size() - 1); }
}
Now only the methods you publish are visible. Use inheritance only when the subclass is truly an extension of the parent, not a use of it.
12. Common newcomer pitfalls¶
Pitfall 1: shadowing fields instead of overriding
class Animal { int legs = 4; }
class Snake extends Animal { int legs = 0; } // SHADOW, not override
Animal a = new Snake();
System.out.println(a.legs); // 4 — fields are dispatched statically
Fields are not polymorphic. Always use methods (and getters) for polymorphic behavior.
Pitfall 2: accidentally overloading instead of overriding
class Animal { void speak() { } }
class Dog extends Animal { void speak(String volume) { } } // overload, not override
Without @Override, you wouldn't notice. Always use @Override.
Pitfall 3: assuming subclass constructor "inherits" parent's
class Animal { Animal(String name) { } }
class Dog extends Animal { /* no ctor */ } // compile error: implicit super() not found
Constructors are not inherited. You either get the parent's no-arg or you write super(...) explicitly.
13. When to use inheritance¶
Use it when: - "B is-a A" is true in the domain (Dog is-a Animal) - B reuses A's behavior nearly verbatim, with small variations - A is designed for extension (open in Kotlin, documented hooks in Java) - Polymorphism through A's reference is needed (collections of A holding B's, methods accepting A)
Avoid it when: - "B has-a A" or "B uses A" — use composition - Reusing A's code by extending is a shortcut to skip writing the right abstraction - A wasn't designed for extension (no protected hooks, undocumented invariants, marked final)
14. Quick reference¶
| Keyword | Meaning |
|---|---|
extends | "this class is a subclass of …" |
super | reference to parent's constructor, method, or field |
super(...) | call parent's constructor |
super.X | call parent's method X or read parent's field X |
final | cannot be extended/overridden/reassigned |
@Override | annotation: "I intend to override the parent's method" |
instanceof | runtime type check (also pattern-matching syntax) |
15. What's next¶
| Question | Read |
|---|---|
| How does dynamic dispatch actually work? | senior.md |
| What is the Liskov Substitution Principle? | middle.md |
| Multiple inheritance via interfaces, default methods | 06-interfaces |
| Sealed classes (Java 17+) | senior.md |
| The diamond problem and how Java prevents it | professional.md |
Memorize this: Inheritance models is-a. Use extends only when the subclass truly is a kind of the parent. Use @Override to make intent explicit. Use composition by default; inheritance is the special case, not the default.