HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
4 Topics
Chapter•85 Minutes

Object-Oriented Programming

Build robust Java models using classes, constructors, encapsulation, inheritance, polymorphism, interfaces, composition, records, and sealed types.

Classes, Objects, Fields, and Constructors

A class defines state and behavior; an object is a runtime instance. Constructors establish valid initial state. Fields should normally be private, with behavior exposed through methods rather than unrestricted mutation.

public final class User {
    private final String id;
    private String displayName;

    public User(String id, String displayName) {
        this.id = id;
        this.displayName = displayName;
    }

    public String id() { return id; }
    public String displayName() { return displayName; }
}

Favor constructors that prevent invalid states. If a field must never change after construction, make it final.

Key Takeaways
  • Constructors should establish invariants.
  • Encapsulation keeps object state under controlled mutation.
  • Prefer immutable fields when the domain allows it.

Encapsulation, Inheritance, and Polymorphism

Encapsulation hides implementation details behind a stable API. Inheritance should model a genuine "is-a" relationship. Polymorphism allows code to depend on an abstraction while the runtime object determines the implementation.

interface PaymentProcessor {
    void pay(long cents);
}

final class CardPayment implements PaymentProcessor {
    public void pay(long cents) {
        System.out.println("Card: " + cents);
    }
}

final class Checkout {
    private final PaymentProcessor processor;

    Checkout(PaymentProcessor processor) {
        this.processor = processor;
    }

    void complete(long cents) {
        processor.pay(cents);
    }
}

Checkout does not care whether payment uses a card, wallet, or test double. This is dependency inversion through an interface.

Prefer composition

Inheritance creates tight coupling. Use composition when the relationship is really 'has-a' rather than 'is-a'.

Key Takeaways
  • Polymorphism reduces coupling to concrete implementations.
  • Interfaces are excellent boundaries for dependency injection and testing.
  • Composition is often more flexible than deep inheritance hierarchies.

Abstract Classes and Interfaces

An abstract class can hold shared state and implementation while preventing direct instantiation. An interface primarily defines a capability or contract and can contain default, static, and private methods.

A class can implement multiple interfaces but extend only one class.

interface Auditable {
    String actor();
}

abstract class BaseCommand {
    private final long createdAt = System.currentTimeMillis();
    long createdAt() { return createdAt; }
}

final class CreateUserCommand extends BaseCommand implements Auditable {
    public String actor() { return "system"; }
}
Key Takeaways
  • A class has single inheritance but can implement multiple interfaces.
  • Abstract classes are useful when shared state or implementation is intrinsic.
  • Interfaces define capabilities and reduce dependency on concrete classes.

Records, Sealed Classes, and Enums

Modern Java provides concise tools for domain modeling. A record is ideal for transparent, mostly immutable data carriers. A sealed type restricts which types may extend or implement an abstraction. An enum models a fixed set of named constants.

public record Point(int x, int y) {}

public sealed interface Result
        permits Success, Failure {}

public record Success(String value) implements Result {}
public record Failure(String reason) implements Result {}

enum Status {
    NEW, PROCESSING, COMPLETED, FAILED
}

Records generate accessors, a canonical constructor, equals, hashCode, and toString. They do not make referenced objects deeply immutable.

Key Takeaways
  • Records reduce boilerplate for data-centric types.
  • Sealed types make an inheritance boundary explicit.
  • Records are shallowly immutable; referenced mutable objects can still change.