IS-A Relationship
Inheritance models the IS-A relationship where a subclass is a specialized version of its superclass.
The IS-A Test
Ask: "Is [subclass] a [superclass]?"
- Dog IS-A Animal ✓
- SavingsAccount IS-A BankAccount ✓
- Circle IS-A Shape ✓
- Car IS-A Engine ✗ (Car HAS-A Engine)
Inheritance Hierarchy
┌─────────┐
│ Animal │
└────┬────┘
┌────────┴────────┐
┌─────┴─────┐ ┌─────┴─────┐
│ Dog │ │ Cat │
└─────┬─────┘ └───────────┘
│
┌─────┴─────┐
│ Puppy │
└───────────┘
public class Animal {
protected String name;
protected int age;
public void eat() {
System.out.println(name + " is eating");
}
public void sleep() {
System.out.println(name + " is sleeping");
}
}
public class Dog extends Animal {
private String breed;
public void bark() {
System.out.println(name + " is barking");
}
}
public class Puppy extends Dog {
public void play() {
System.out.println(name + " is playing");
}
}
Liskov Substitution Principle (LSP)
Subclasses must be substitutable for their base class without altering correctness:
// Good: LSP satisfied
public class Bird {
public void fly() { ... }
}
public class Sparrow extends Bird {
// Sparrow can fly - LSP satisfied
}
// Bad: LSP violated
public class Ostrich extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException(); // LSP violation!
}
}
Common Inheritance Hierarchies
Domain Models:
PaymentMethod
├── CreditCard
├── DebitCard
├── PayPal
└── BankTransfer
Exception Hierarchies:
Exception
├── IOException
│ ├── FileNotFoundException
│ └── SocketException
├── SQLException
│ ├── ConnectionException
│ └── QueryException
└── IllegalArgumentException
When NOT to Use Inheritance
- When relationship is HAS-A, not IS-A
- When subclass needs fundamentally different behavior
- When hierarchy becomes too deep (3+ levels is a warning)
- When you find yourself overriding most methods
Override vs Overload
Overriding and overloading are often confused. They are fundamentally different mechanisms with different purposes.
Method Overriding
Same method signature in subclass, replacing parent behavior:
public class Animal {
public void speak() {
System.out.println("Some generic sound");
}
}
public class Dog extends Animal {
@Override // Annotation: signals intent to override
public void speak() {
System.out.println("Woof!");
}
}
// Runtime polymorphism
Animal animal = new Dog();
animal.speak(); // Output: Woof! (not "Some generic sound")
Method Overloading
Same method name, different parameter lists (within the same class):
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
// Compile-time polymorphism
Calculator calc = new Calculator();
calc.add(1, 2); // Calls int version
calc.add(1.5, 2.5); // Calls double version
calc.add(1, 2, 3); // Calls 3-param version
Key Differences
| Aspect | Overriding | Overloading |
|---|---|---|
| Where | Subclass | Same class (or parent) |
| Signature | Must match | Must differ |
| Binding | Runtime (dynamic) | Compile-time (static) |
| Purpose | Specialize behavior | Same operation, different inputs |
| @Override | Recommended | Not applicable |
| Return type | Must be same/subtype | Can differ |
| Access | Cannot be more restrictive | Can differ |
Overriding Rules
public class Parent {
protected void display() { }
}
public class Child extends Parent {
// ✓ Can increase visibility
@Override
public void display() { }
// ✗ Cannot decrease visibility
// @Override
// private void display() { } // Compile error!
// ✗ Cannot change return type (except covariant)
// @Override
// int display() { } // Compile error!
// ✓ Can return subtype (covariant return)
@Override
public Object clone() { return new Child(); }
}
Common Mistakes
- Forgetting @Override: Typos create new methods instead of overriding
- Overloading when overriding intended: Different signature = new method
- Changing semantics: Overriding should maintain the contract
- Calling parent method: Forget
super.method()when you need parent behavior
Composition Over Inheritance
Composition over inheritance is a fundamental design principle: prefer containing objects over extending them.
The Problem with Deep Inheritance
┌─────────┐
│ Vehicle │
└────┬────┘
┌────────┴────────┐
┌────┴────┐ ┌─────┴────┐
│ Car │ │ Truck │
└────┬────┘ └──────────┘
│
┌────┴──────────┐
│ SportsCar │
└───────────────┘
Problems:
- Explosion of classes
- Tight coupling to parent
- Changes in parent affect all children
- Diamond problem in multiple inheritance
Composition Solution
// Instead of inheritance, compose behaviors
public class Vehicle {
private Engine engine;
private Transmission transmission;
private GpsNavigator gps;
public Vehicle(Engine engine, Transmission transmission, GpsNavigator gps) {
this.engine = engine;
this.transmission = transmission;
this.gps = gps;
}
public void start() {
engine.start();
transmission.engage();
}
}
// Flexible combinations
Vehicle sportsCar = new Vehicle(
new V8Engine(),
new ManualTransmission(),
new PremiumGps()
);
Vehicle electricCar = new Vehicle(
new ElectricEngine(),
new NoTransmission(),
new BasicGps()
);
HAS-A vs IS-A
IS-A (Inheritance) HAS-A (Composition)
───────────────── ──────────────────
Dog IS-A Animal Car HAS-A Engine
Savings IS-A Account Order HAS-A PaymentMethod
Square IS-A Shape Server HAS-A Logger
Use when: Use when:
- Subtype relationship - Assembling behaviors
- Subclass IS the parent - Multiple capabilities
- Hierarchy is shallow - Flexibility needed
Benefits of Composition
| Benefit | Explanation |
|---|---|
| Flexibility | Change behavior at runtime |
| Testability | Easy to mock dependencies |
| Loose coupling | Components independent |
| Reuse | Behaviors shared across unrelated classes |
| No hierarchy explosion | Flat structure |
Strategy Pattern with Composition
// Define behavior as interface
public interface SortStrategy {
void sort(int[] array);
}
public class QuickSort implements SortStrategy {
public void sort(int[] array) { /* quicksort */ }
}
public class MergeSort implements SortStrategy {
public void sort(int[] array) { /* mergesort */ }
}
// Compose the behavior
public class Sorter {
private SortStrategy strategy;
public Sorter(SortStrategy strategy) {
this.strategy = strategy;
}
public void sort(int[] array) {
strategy.sort(array);
}
// Change behavior at runtime!
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
}
When to Use Inheritance
Despite composition being preferred, inheritance is still valid for:
- Clear IS-A relationships
- Framework requirements (extending framework classes)
- Template Method pattern
- Shallow hierarchies (1-2 levels)
Practice Problems
Design a scalable Inheritance system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Inheritance to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Inheritance and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What does the IS-A test check?
2. What is the key difference between overriding and overloading?
3. Why is composition preferred over inheritance?
4. What is the Liskov Substitution Principle?
5. When is inheritance still appropriate despite composition being preferred?
Flashcards
Question
What is the IS-A relationship?
Click to reveal answer
Answer
A relationship where a subclass is a specialized type of its superclass. Tested by asking 'Is [subclass] a [superclass]?' Dog IS-A Animal is valid.
Question
What is method overriding?
Click to reveal answer
Answer
Redefining a parent class method in a subclass with the same signature. Uses @Override annotation. Enables runtime polymorphism.
Question
What is method overloading?
Click to reveal answer
Answer
Multiple methods with the same name but different parameter lists in the same class. Resolved at compile-time. Enables same operation with different inputs.
Question
What is Composition over Inheritance?
Click to reveal answer
Answer
A design principle preferring object composition (HAS-A) over class inheritance (IS-A). Provides flexibility, testability, and loose coupling.
Question
What is the Liskov Substitution Principle?
Click to reveal answer
Answer
Objects of a subclass should be substitutable for objects of the superclass without breaking program correctness. Subclasses must honor the parent's contract.
Revision Notes
Key Takeaways
- 1.Inheritance models IS-A relationships; composition models HAS-A
- 2.Method overriding replaces parent behavior; overloading adds new signatures
- 3.Composition provides more flexibility and testability than inheritance
- 4.LSP requires subclasses to be substitutable for their base class
- 5.Prefer composition unless there's a clear, shallow IS-A relationship
Interview Tips
- •Always test IS-A relationships: 'Dog IS-A Animal' is valid, 'Car IS-A Engine' is not
- •Mention LSP when discussing inheritance hierarchies in design
- •Show composition examples with strategy or dependency injection patterns
- •Discuss why deep inheritance (3+ levels) is often a design smell
Cheat Sheet
Inheritance - Cheat Sheet
IS-A Test:
"Is [subclass] a [superclass]?" ✓ Dog IS-A Animal
"Is [class] a [part]?" ✗ Car IS-A Engine → Use composition
Override vs Overload:
| Override | Overload | |
|---|---|---|
| Location | Subclass | Same class |
| Signature | Same | Different |
| Binding | Runtime | Compile-time |
| @Override | Yes | N/A |
LSP:
Subclasses must be substitutable for base class.
Ostrich extending Bird and throwing on fly() violates LSP.
Composition > Inheritance:
- Flexibility: change behavior at runtime
- Testability: easy to mock
- Loose coupling: independent components
- No hierarchy explosion
When to use inheritance:
- Clear IS-A relationship
- Shallow hierarchy (1-2 levels)
- Framework requirements
- Template Method pattern