Inheritance and the 'is-a' Relationship โ Revision Notes
Inheritance is an OOP pillar and a design-judgement question: interviewers ask "inheritance vs composition" to see if you know when not to inherit. The diamond problem and constructor order are common deeper probes.
What inheritance is
Inheritance lets a derived (child) class reuse and extend a base (parent) class, modelling an "is-a" relationship (a Dog is an Animal). The child inherits the parent's members and can add or override behaviour.
Types of inheritance
| Type | Structure |
|---|---|
| Single | one parent โ one child |
| Multilevel | A โ B โ C |
| Hierarchical | one parent, many children |
| Multiple | many parents (classes: C++ only, not Java) |
The diamond problem
With multiple inheritance, if two parents share a common grandparent, the child gets an ambiguous duplicate. C++ solves it with virtual inheritance; Java sidesteps it by disallowing multiple class inheritance.
Constructor order
Constructors run base-class first, then derived (the parent must be fully built before the child).
Exam Tricks & Tips
- ๐ฏ Inheritance models "is-a"; composition models "has-a" โ choosing the wrong one is a classic design smell.
- ๐ฏ Favour composition over inheritance when behaviour is shared but there's no true "is-a" โ the standard best-practice answer.
- ๐ฏ The diamond problem is ambiguous multiple inheritance; C++ uses virtual inheritance, Java avoids it by allowing only single class inheritance.
- ๐ฏ Constructors execute parent-first, child-last; destructors (C++) run in reverse order.
- ๐ฏ Deep inheritance hierarchies increase coupling and fragility (the fragile base class problem) โ keep them shallow.
- ๐ฏ A subclass is-a superclass, so it can be used wherever the superclass is expected (Liskov substitution).
- โ Common mistake: using inheritance for code reuse alone when there's no "is-a" โ leads to brittle designs; use composition instead.
Expected pattern
"Inheritance vs composition", "what is the diamond problem?", "constructor call order in inheritance", "types of inheritance", and "is-a vs has-a".
Quick recap
Inheritance = "is-a" reuse/extension; child inherits + overrides parent. Types: single/multilevel/hierarchical/multiple (classes: not in Java). Diamond problem = ambiguous multiple inheritance (C++ virtual inheritance solves it). Constructors run parentโchild. Favour composition ("has-a") when there's no true is-a.
Inheritance and the 'is-a' Relationship โ Flashcards
Cover the answer, recall, then check. 12 cards on inheritance.
Q1. What relationship does inheritance model?
A1. An "is-a" relationship โ a derived class is a kind of its base class (a Dog is an Animal).
Q2. What does a subclass inherit from its superclass?
A2. The accessible members (fields and methods); it can add new ones and override inherited behavior.
Q3. Inheritance vs composition?
A3. Inheritance = "is-a" (extend a base); composition = "has-a" (contain other objects). Prefer composition without a true is-a.
Q4. What is the diamond problem?
A4. Ambiguity in multiple inheritance when two parents share a common ancestor, giving the child a duplicated/ambiguous member.
Q5. How does C++ resolve the diamond problem?
A5. Virtual inheritance โ the shared base is inherited once, removing the duplication.
Q6. How does Java avoid the diamond problem?
A6. It disallows multiple class inheritance; classes can only implement multiple interfaces.
Q7. In what order do constructors run in an inheritance chain?
A7. Base (parent) constructor first, then derived (child) โ the parent must be fully initialized first.
Q8. Name the types of inheritance.
A8. Single, multilevel (AโBโC), hierarchical (one parent, many children), and multiple (classes only in C++).
Q9. Why favor composition over inheritance?
A9. It's more flexible and less coupled; inheritance can create fragile hierarchies where base changes break subclasses.
Q10. What is the fragile base class problem?
A10. Changes to a base class unexpectedly breaking derived classes, due to tight coupling in deep hierarchies.
Q11. What principle says a subclass should be substitutable for its base?
A11. The Liskov Substitution Principle โ objects of a subclass should work wherever the base type is expected.
Q12. Does Java support multiple inheritance of classes?
A12. No โ only single class inheritance; multiple inheritance of type is achieved via interfaces.
Inheritance and the 'is-a' Relationship
Inheritance lets a class reuse and extend another's members. It is the OOP pillar most often misused โ beginners inherit for code reuse when they should compose โ so interviewers probe whether you know when it actually fits.
Core idea: inheritance creates an "is-a" relationship: a subclass (derived/child) is a specialised kind of its superclass (base/parent), inheriting its fields and methods and optionally adding or overriding behaviour. A Dog is an Animal; a SavingsAccount is a BankAccount.
How it works
Beginner โ extending a base
class Animal {
String name;
void eat() { System.out.println(name + " eats"); }
}
class Dog extends Animal { // Dog inherits name + eat()
void bark() { System.out.println("woof"); }
}
Dog d = new Dog();
d.eat(); // inherited
d.bark(); // added
Dog gets everything Animal has, for free, plus its own members.
Intermediate โ overriding and super
A subclass can override an inherited method to specialise it, and call the parent version via super:
class Cat extends Animal {
@Override
void eat() {
super.eat(); // reuse parent behaviour
System.out.println("delicately");
}
}
Constructors are not inherited but are chained: a subclass constructor implicitly calls super() first so the parent part of the object is initialised before the child part.
Advanced โ single vs multiple, and the fragile-base-class problem
Java/C# allow single class inheritance (one base) to avoid the diamond problem โ the ambiguity when two parents provide the same method. C++ allows multiple inheritance and resolves diamonds with virtual base classes. A deeper caution is the fragile base class problem: changing a base class can silently break subclasses that depended on its internals. This is why the guidance is often "favour composition over inheritance" โ an object that has-a engine is more flexible than one that is-an engine.
Worked example โ is-a vs has-a
// GOOD: genuine is-a
class ElectricCar extends Car { ... } // an ElectricCar IS A Car
// BAD: forcing is-a for reuse
class Stack extends ArrayList { ... } // a Stack is NOT AN ArrayList!
// -> exposes add(index, e), remove(index)... breaking stack invariants
// BETTER: composition (has-a)
class Stack {
private final List<Integer> items = new ArrayList<>(); // Stack HAS A list
void push(int x) { items.add(x); }
int pop() { return items.remove(items.size()-1); }
}
Extending ArrayList leaks unwanted operations that violate LIFO. Composing a private list exposes only stack operations. The test: say "X is-a Y" out loud โ if it sounds wrong, use composition.
Interview & exam relevance
Common asks: "what is the is-a relationship / when to use inheritance vs composition", "the diamond problem and how languages avoid it", "does a subclass inherit constructors?" (no โ but they are chained via super), and "what is method overriding?" Expect the phrase "favour composition over inheritance" and be ready to justify it.
Tricks & gotchas
- Use inheritance only for a genuine "is-a"; use composition ("has-a") for reuse without a subtype relationship.
- Overriding replaces behaviour; overloading is unrelated (same name, different params).
- The subclass object is fully constructed only after
super()finishes โ don't call overridable methods from a constructor. - Mnemonic: "is-a โ inherit; has-a โ compose."
Using inheritance for code reuse when the relationship is not truly "is-a" (e.g. Stack extends ArrayList). The subclass inherits methods that break its own invariants and becomes tightly coupled to the base's internals. If you cannot honestly say "child is-a parent", compose the functionality instead of inheriting it.
- โ- Inheritance models "is-a": a subclass specialises a superclass, reusing its members.
- โ- Override to specialise; call
superto reuse the parent version. - โ- Constructors aren't inherited but are chained (
super()runs first). - โ- Single inheritance (Java) avoids the diamond problem; C++ uses virtual bases.
- โ- Favour composition (has-a) over inheritance for reuse without a subtype relationship.
- โInheritance expresses a genuine "is-a" specialisation, giving reuse plus overriding and constructor chaining. Misapplied for mere code reuse it creates fragile, over-coupled hierarchies โ so reserve it for true subtypes and reach for composition otherwise.
Inheritance and the 'is-a' Relationship โ Worked Example
Worked Example
Problem: A team makes Square extend Rectangle because "a square is a rectangle". Show the code that breaks, and explain why inheritance was the wrong tool (the Liskov test).
class Rectangle {
protected int w, h;
void setWidth(int x) { w = x; }
void setHeight(int x) { h = x; }
int area() { return w * h; }
}
class Square extends Rectangle { // "is-a" ... really?
void setWidth(int x) { w = h = x; } // keep sides equal
void setHeight(int x) { w = h = x; }
}
Solution:
Inheritance should model a true is-a relationship where a subtype is substitutable for its base (the Liskov Substitution Principle): any code correct for Rectangle must stay correct when handed a Square.
Consider client code written against Rectangle:
void resizeAndCheck(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
assert r.area() == 20; // holds for a real Rectangle
}
Pass a Square: setWidth(5) sets both sides to 5, then setHeight(4) sets both to 4, so area() = 16, not 20 โ the assertion fails. Square silently violates a guarantee the base class made (independent width and height). Even though geometrically a square is a rectangle, behaviourally Square is NOT substitutable for the mutable Rectangle, so the inheritance is unsound.
Better designs: make the shapes immutable (no setters, so the conflict disappears), or drop the inheritance and have both implement a Shape interface with area(), or use composition. The lesson: "is-a in English" is not enough โ the subtype must honour the base type's behavioural contract.
Answer: resizeAndCheck expects area 20 but a Square yields 16 because its overridden setters couple the sides, breaking Rectangle's contract. Square fails the Liskov substitution test, so it should not inherit from a mutable Rectangle โ use a shared interface or immutability instead.
- โ- Inheritance models is-a only if the subtype is fully substitutable for the base (Liskov Substitution).
- โ- A subtype that weakens or contradicts the base's behavioural guarantees breaks callers written to the base.
- โ- When substitutability fails, prefer a common interface, composition, or immutability over inheritance.