Extend behaviour with inheritance and achieve flexibility with polymorphism.
Published September 21, 2026
Inheritance lets one class be defined as a specialized version of another: a SavingsAccount is a BankAccount with interest. The subclass automatically gets the parent's fields and methods, and can add new ones or change inherited behaviour.
Polymorphism ("many forms") is what makes this useful. Code written against the parent type works with any subclass, and each object responds in its own way. The caller doesn't need to know which one it has.
List<Shape> shapes = List.of(new Circle(1), new Rectangle(2, 3), new Triangle(3, 4));
double total = 0;
for (Shape s : shapes) total += s.area(); // each object computes ITS OWN area
The loop is written once and never changes, even when new shapes are added later. That's the practical goal: code that's open to new types without being edited.
extendspublic abstract class Employee {
private final String name;
protected final long baseSalary; // protected: visible to subclasses
protected Employee(String name, long baseSalary) {
this.name = name;
this.baseSalary = baseSalary;
}
public String getName() { return name; }
public long monthlyPay() { // default behaviour
return baseSalary / 12;
}
}
public class Manager extends Employee {
private final long bonus;
public Manager(String name, long baseSalary, long bonus) {
super(name, baseSalary); // parent constructor runs first (must be the first statement)
this.bonus = bonus;
}
@Override
public long monthlyPay() { // specialized behaviour
return super.monthlyPay() + bonus / 12; // reuse the parent's version, then extend it
}
}
Key rules:
extends exactly one class (single inheritance). Every class ultimately extends Object.super(...), either explicitly or implicitly calling the no-argument one.private members aren't accessible in subclasses. protected members are, and are also visible within the same package.super.method() calls the parent's version from an overriding method.A subclass overrides a method by declaring one with the same name and parameter types. Which version runs is decided at runtime, from the actual object, not from the variable's declared type. This is called dynamic dispatch:
Employee e = new Manager("Priya", 2_400_000, 600_000); // declared type Employee, actual type Manager
e.monthlyPay(); // runs Manager.monthlyPay()
The overriding rules exist so that a subclass can always stand in for its parent:
@Override. If the signature doesn't actually match (a typo, or wrong parameter types), the compiler reports it instead of silently creating a new, unrelated method.static, private and final methods can't be overridden. A static method with the same signature in a subclass hides the parent's rather than overriding it, and which one runs depends on the declared type.Overloading means several methods share a name but have different parameter lists. The compiler picks which to call from the declared types of the arguments:
void print(int x) { ... }
void print(String s) { ... }
void print(Object o) { ... }
Object o = "hello";
print(o); // calls print(Object) โ chosen at compile time from the DECLARED type, not the runtime type
| Overriding | Overloading | |
|---|---|---|
| Where | Subclass redefines a parent method | Same class (or subclass), same name |
| Signature | Must be the same | Must differ in parameters |
| Decided | At runtime (actual object type) | At compile time (declared argument types) |
| Purpose | Specialize behaviour | Convenience variants of one operation |
Mixing them up is one of the most common interview traps. The print(o) example above is the classic question.
instanceofEmployee e = new Manager(...); // upcast: always safe, implicit
Manager m = (Manager) e; // downcast: explicit; throws ClassCastException if e isn't a Manager
if (e instanceof Manager mgr) { // pattern matching (Java 16+): test and cast in one step
mgr.approveBudget();
}
Frequent downcasting is a design smell. It usually means behaviour that should be a polymorphic method (e.approve()) is instead being chosen with type checks.
finalabstract class can't be instantiated. It can declare abstract methods (no body) that concrete subclasses must implement, while sharing fields and implemented methods. Employee above could make monthlyPay() abstract if no sensible default exists.final class can't be extended (String is final). A final method can't be overridden. Use final when a class's behaviour must not be altered, for security, immutability, or because it simply wasn't designed for extension.sealed class (Java 17) allows only listed subclasses, a middle ground covered in Sealed Classes.Inheritance is a strong coupling: the subclass depends on the parent's implementation details, not just its interface. Classic problems:
Square extends Rectangle whose setWidth also changes the height breaks code that expects a rectangle's sides to be independent. If a subclass can't honour the parent's contract, it shouldn't be a subclass.Stack extends Vector (as in old Java) also exposes insertElementAt, which breaks stack semantics.The widely followed guideline is "favour composition over inheritance": give a class a field of another type and delegate to it (a has-a relationship), and implement interfaces for polymorphism. Use inheritance when there's a genuine is-a relationship and the subclass honours everything the parent promises.
// Composition: the service HAS a notifier; any implementation can be plugged in
public class OrderService {
private final Notifier notifier; // interface
public OrderService(Notifier notifier) { this.notifier = notifier; }
}
Q: What's the difference between overriding and overloading? A: Overriding redefines an inherited method with the same signature, and the version used is chosen at runtime from the actual object (dynamic dispatch). Overloading defines methods with the same name but different parameters, and the version is chosen at compile time from the declared argument types.
Q: Can you override a static method? A: No. A static method with the same signature in a subclass hides the parent's. The version called depends on the reference's declared type (or the class name used), not on the object, so there's no polymorphism.
Q: Why doesn't Java support multiple inheritance of classes? A: To avoid the ambiguity and complexity of inheriting conflicting state and implementations from two parents (the "diamond problem"). Java allows multiple inheritance of type through interfaces. When two interfaces supply conflicting default methods, the class must override the method and resolve the conflict explicitly.
Q: What is the Liskov Substitution Principle?
A: Objects of a subclass must be usable anywhere the parent type is expected, without breaking correctness. The subclass may not strengthen preconditions, weaken postconditions, or violate the parent's invariants. Square extends Rectangle is the textbook violation.
Q: When would you choose inheritance over composition? A: When there's a true is-a relationship, the parent was designed for extension (with documented extension points), and subclasses fully honour its contract. Frameworks often use this deliberately, with abstract base classes and template methods. Otherwise, composition plus interfaces gives looser coupling and easier testing.