LESSON 09 · BEGINNER4 min read

Core OOP Principles & Dynamic Method Dispatch

Revisit object-oriented foundations. Understand encapsulation, inheritance trade-offs, virtual method tables (vtable), and dynamic polymorphism.

OpenJDK 21+ (LTS)Free to readModule 3 of 15
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Contrast encapsulation, abstraction, inheritance, and runtime polymorphism.
  • Understand the Virtual Method Table (vtable) and dynamic method dispatch mechanics.
  • Apply the 'Composition over Inheritance' design principle.

01The Pillars of Object-Oriented Architecture

Object-Oriented Programming (OOP) organizes software into cohesive, self-contained entities called Objects that combine state (fields) and behavior (methods). The four classic pillars of OOP are Encapsulation, Abstraction, Inheritance, and Polymorphism.

Encapsulation hides internal state behind access modifiers (private, protected, public) and exposes controlled operations. Abstraction separates what an object does from how it accomplishes it. Inheritance allows code sharing, while Polymorphism allows different object types to respond to the same method invocation dynamically.

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Dynamic Method Dispatch and the vtable

How does the JVM know which method implementation to execute when you invoke account.processPayment(amount) on a parent reference? The answer is Dynamic Method Dispatch, mediated by the Virtual Method Table (vtable).

Every class loaded into memory possesses a vtable: an array of memory pointers pointing to its concrete method implementations. When a child class overrides a parent method, the child's vtable entry points to the overridden code. At runtime, invokevirtual performs an instant O(1) table offset lookup, executing the subclass code without expensive type-checking cascades.

notes.txtPlain text
+---------------------------------------------------+|            vtable Dynamic Method Dispatch         |+---------------------------------------------------+| Parent vtable: [ 0: toString ] [ 1: processPayment]-> DefaultImpl| Child vtable:  [ 0: toString ] [ 1: processPayment]-> CryptoImpl| (invokevirtual queries index 1 of the concrete instance's vtable)|+---------------------------------------------------+

03Composition Over Inheritance

While inheritance allows code reuse, deep inheritance hierarchies create tight coupling, fragile base class bugs, and unexpected side effects. If a parent class modifies an internal method, it can silently break overridden behavior in child subclasses.

The professional design principle is Composition over Inheritance: construct complex behaviors by assembling independent collaborator objects rather than extending classes. Use inheritance strictly when a genuine 'is-a' relationship exists with behavioral substitution (Liskov Substitution Principle).

example.js☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04Why This Matters in Production

In production codebases with millions of lines of code, leaky encapsulation and fragile inheritance chains are the primary drivers of technical debt. When private fields are exposed via unvalidated getters and setters, external components mutate state arbitrarily, invalidating invariants.

By enforcing strict immutability, validating data inside constructors, and favoring composition, you create robust domain models that can be refactored safely without cascading failures.

05Production Architecture & Engineering Trade-Offs

In large-scale enterprise codebases, rigorous domain modeling relies on enforcing structural invariants at the type level rather than relying on defensive runtime validation. Modern Java leverages sealed types and records to turn runtime type errors into compile-time exhaustiveness guarantees. When designing domain transfer objects or persistent entities, ensuring immutability protects against unintended side effects across concurrent worker threads.

Furthermore, avoiding deep inheritance hierarchies in favor of composition and interface contracts eliminates fragile base class anti-patterns. By encapsulating state mutations within well-defined service boundaries and exposing pure functional transformers, software architects build maintainable, audit-compliant systems that scale smoothly across distributed cloud infrastructure.

LET IT CLICK

The ideas worth keeping.

  • Encapsulation defends domain invariants behind strict visibility barriers.
  • Virtual method calls use vtable index lookups for O(1) dynamic dispatch.
  • Favor composition over inheritance to build modular, maintainable enterprise systems.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Defending Domain Invariants

Design an immutable BankAccount record that validates non-negative balance and non-empty owner name inside a compact constructor.

your_turn_1.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
public record BankAccount(String owner, long balance) {    public BankAccount {        if (owner == null || owner.isBlank()) throw new IllegalArgumentException("Owner required");        if (balance < 0) throw new IllegalArgumentException("Balance cannot be negative");    }}

Compact constructors in records validate parameters before field assignment, guaranteeing invariant safety.

02

Exercise 2: Refactoring Inheritance to Composition

Refactor a class SecureLogger extending FileLogger to instead wrap a Logger instance via composition.

your_turn_2.java☕Java
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.javaJava
public class SecureLogger {    private final Logger delegate;    public SecureLogger(Logger delegate) { this.delegate = delegate; }    public void log(String msg) { delegate.log(sanitize(msg)); }    private String sanitize(String m) { return m.replaceAll("password=.*", "***"); }}

Wrapping the delegate logger allows intercepting and sanitizing messages without coupling to base class internals.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01How does the JVM perform dynamic method dispatch for virtual method invocations?
02What is the primary danger of deep inheritance hierarchies?
03What is the core recommendation of 'Composition over Inheritance'?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Defending Domain Invariants

Design an immutable BankAccount record that validates non-negative balance and non-empty owner name inside a compact constructor.

EXPECTED OUTPUT
Build successful. (Java 15)
[Process exited normally]
Main.javaJava
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.