Skip to content
Free public beta until December 4, 2026: every course is open, no card needed. After that, the first chapter of each course stays free and full access is a one-time $39 ($19 for up to 100 founding members chosen for helpful feedback). Your progress is kept. How the beta works

Every way to vary behaviour

Subclass and override, abstract classes, interfaces, default methods, lambdas, sealed types with a switch, composition and Strategy.

Ask where the variation lives and who adds new cases.

  • A fixed, known set of cases, each with different data → a sealed interface of records, and an exhaustive switch with patterns. The compiler tells you every place a new case must be handled.
  • An open set: other code adds new kinds → an interface, one class per kind, and dynamic dispatch.
  • One step of an algorithm varies → pass it in: a lambda for a functional interface, or a Strategy object.
  • Shared code and shared state among closely related classes → an abstract class. Prefer composition when the relationship isn't truly "is a".

Through inheritance

Reusing and extending a superclass.

The object's class picks the body; the declared type picks the overload.

Choosing which override runs from the object's class while the program runs.

Choosing the override at run time from the object's class.

Shared code plus methods each subclass must supply.

Through interfaces and lambdas

Section

Interfaces

A contract any class can sign.

An interface method with a body, marked default, inherited by every implementing class.

An interface method with a body, so implementers can skip it.

An interface with exactly one abstract method, which a lambda can implement.

Example

Comparator<Order> byTotal = (a, b) -> Integer.compare(a.total(), b.total());

One abstract method, so a lambda can implement it.

Swapping a step of an algorithm behind an interface.

Through data and a switch

A class or interface that names every type allowed to extend or implement it.

A type whose subtypes are all listed, so a switch can cover them.

Take a record apart in a case, and let the compiler check you covered every subtype.

When 'has a' beats 'is a'.