Java 16+ - eliminates the cast-after-check boilerplate
Sealed classes (Java 17+)
Closed hierarchy - compiler knows every subtype; exhaustive switch without default
Liskov Substitution Principle
Every subtype must be substitutable for its parent without breaking correctness
Inheritance Memory Model
Object
│
┌────────┴────────┐
Animal (other)
fields: name, age
method: eat(), sleep()
│
┌──────┴──────┐
Dog Cat
field: breed field: indoor
override: speak() override: speak()
add: fetch()
Stack Heap
───── ────────────────────────────
Animal a ───────────────► Dog object
├─ Animal part: name="Rex", age=3
└─ Dog part: breed="Labrador"
// a.speak() → Dog.speak() (runtime dispatch on actual type)
// a.fetch() → compile error (reference type is Animal)
Method Dispatch (Virtual Method Table)
compile time runtime
───────────── ─────────────────────────
Animal a = new Dog(...)
a.speak() ───────────────► Dog.speak() ← actual type wins
a.eat() ───────────────► Animal.eat() ← not overridden, falls back
Casting Rules
Widening (always safe): Dog d = new Dog();
Animal a = d; // implicit
Narrowing (risky): Animal a = new Dog();
Dog d = (Dog) a; // explicit, ClassCastException if wrong
Pattern match (Java 16+): if (a instanceof Dog dog) {
dog.fetch(); // no cast needed - dog is Dog
}
Switch pattern (Java 21): switch (shape) {
case Circle c -> ...
case Rectangle r -> ...
}
Sealed Class Hierarchy
sealed interface Shape permits Circle, Rectangle, Triangle {}
// Compiler knows the COMPLETE set of subtypes.
// switch on Shape can be exhaustive without default:
String area = switch (shape) {
case Circle c -> "π·r²";
case Rectangle r -> "w·h";
case Triangle t -> "½·b·h";
// no default needed - sealed guarantees exhaustiveness
};
Liskov Substitution Principle
Rule: if S extends P, then anywhere P is expected, S must work correctly.
VIOLATION example:
class Rectangle { setWidth(w); setHeight(h); area() = w*h; }
class Square extends Rectangle {
setWidth(w) { super.setWidth(w); super.setHeight(w); } // ← breaks LSP
setHeight(h) { super.setWidth(h); super.setHeight(h); }
}
// Code that expects Rectangle:
Rectangle r = new Square();
r.setWidth(5); r.setHeight(3);
assert r.area() == 15; // FAILS - Square made it 9