Level 1 · 20 min
Inheritance & Polymorphism
Inheritance enables code reuse by deriving new classes from existing ones. Polymorphism allows different classes to be used interchangeably when they share a common interface. Together they enable the open/closed principle.
Inheritance and Overriding
Inheritance (extends) creates an is-a relationship. Method overriding allows a subclass to provide a specific implementation of a method defined in the base class. @Override annotation catches mistakes. Overloading (same name, different parameters) is different — it's resolved at compile time (static dispatch), not runtime (dynamic dispatch).
Polymorphism and Dynamic Dispatch
Polymorphism means 'many forms' — the same method call behaves differently based on the actual object type at runtime. Dynamic dispatch: the JVM uses a virtual method table (vtable) to look up the correct method implementation at runtime. Animal animal = new Dog() → animal.speak() calls Dog's speak(), not Animal's. This is the foundation of the OCP pattern. Bloch's Item 19 (Effective Java) captures the deeper issue: "To document a class so that it can be safely subclassed, you must describe implementation details that should otherwise be left unspecified" — demonstrating that inheritance violates encapsulation by definition. His recommendation: if you haven't designed a class for inheritance, declare it final or prohibit subclassing entirely. The JDK's AbstractList is a well-documented counterexample: its @implSpec tags explicitly describe how iterator() affects remove() so subclasses can override safely.
LSP in Inheritance Hierarchies
LSP: if S is a subtype of T, objects of type T may be replaced with objects of type S without altering correctness. Rectangle.setWidth(w) and setHeight(h) work independently. Square extends Rectangle but can't honor this contract — setting width must also set height. The classic LSP violation. Solution: Square should not extend Rectangle; both should implement a Shape interface.
Code example
// Polymorphism: different implementations, same interface
abstract class Shape {
abstract double area();
String describe() { return "Shape with area " + area(); } // template method
}
class Circle extends Shape {
private double radius;
@Override double area() { return Math.PI * radius * radius; }
}
class Rectangle extends Shape {
private double width, height;
@Override double area() { return width * height; }
}
// Polymorphic usage — works for any Shape
double totalArea(List<Shape> shapes) {
return shapes.stream().mapToDouble(Shape::area).sum();
}
// Adding Triangle requires zero changes here