Objects, Classes & OOP Design
Inheritance & Polymorphism
Inheritance (
extends) models strict is-a relationships: subclasses inherit and override behavior, and dynamic dispatch picks the override at runtime. Powerful — and so easily misused that the default advice is: design for it or prohibit it.- Dynamic dispatch: the runtime type of the object decides which override runs
super.method()calls the superclass version;super(...)must be the first constructor statement- Overrides can widen visibility and narrow the return type (covariant returns) — never the reverse
- Use
@Overrideon every override so the compiler catches signature typos (EJ 40) finalclasses/methods forbid subclassing/overriding- Prefer pattern matching
instanceofwhen you must check types
public class Manager extends Employee {
private double bonus;
public Manager(String name, double salary) {
super(name, salary); // must run the superclass constructor first
}
@Override
public double getSalary() {
return super.getSalary() + bonus; // reuse, then extend
}
}Polymorphism: an Employee variable can refer to a Manager; calling getSalary() dispatches on the actual object at runtime. Liskov substitution is the design test — anywhere an Employee works, a Manager must work too. If a subclass would need to "un-inherit" behavior, the is-a relationship is wrong: reach for composition instead.
Casts and type tests
if (staff instanceof Manager m) { // test + cast + bind in one step
m.setBonus(5000);
}Frequent instanceof chains signal a design smell — usually the logic belongs in an overridden method (tell, don't ask), or the hierarchy should be a sealed type with an exhaustive switch (Sealed Types Overview). Abstract classes hold shared code and abstract methods that subclasses must implement; a class with any abstract method is itself abstract and uninstantiable.