OOP

18. The Four Pillars of OOP

Object-oriented programming rests on four pillars: encapsulation, inheritance, polymorphism, and abstraction โ€” each with a distinct purpose and Java mechanism.

Java 21 (core language)

Explanation

Encapsulation: bundling state and behaviour together and restricting direct access to state via access modifiers (private fields + public getters/setters or records). Goal: protect invariants and allow implementation changes without breaking callers.

Inheritance: a subclass inherits fields and methods from a superclass using extends. Enables code reuse and establishes an "is-a" relationship. Java supports single-class inheritance but multiple interface implementation.

Polymorphism: a single interface or supertype can refer to objects of different subtypes. At runtime the actual subtype's method is called (dynamic dispatch / virtual dispatch). The most powerful form in Java is method overriding + upcasting.

Abstraction: hiding implementation details and exposing only what callers need. Achieved through abstract classes (partial implementation) and interfaces (pure contract). Records and sealed types add structured abstraction for data modelling.

Example

public class FourPillars {
    // ENCAPSULATION: private state + controlled access
    static class BankAccount {
        private double balance;
        BankAccount(double initialBalance) { this.balance = initialBalance; }
        public double getBalance() { return balance; }
        public void deposit(double amount) {
            if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
            balance += amount;
        }
    }

    // ABSTRACTION: interface exposes contract, hides implementation
    interface Drawable { void draw(); }

    // INHERITANCE: Dog extends Animal
    static class Animal {
        String name;
        Animal(String name) { this.name = name; }
        String sound() { return "..."; }
    }

    // POLYMORPHISM: subclass overrides sound()
    static class Cat extends Animal implements Drawable {
        Cat(String name) { super(name); }
        @Override String sound() { return "Meow"; }
        @Override public void draw() { System.out.println("Drawing a cat ๐Ÿฑ"); }
    }

    static class Dog extends Animal implements Drawable {
        Dog(String name) { super(name); }
        @Override String sound() { return "Woof"; }
        @Override public void draw() { System.out.println("Drawing a dog ๐Ÿถ"); }
    }

    public static void main(String[] args) {
        // Encapsulation
        BankAccount acc = new BankAccount(100);
        acc.deposit(50);
        System.out.println("Balance: " + acc.getBalance());

        // Polymorphism โ€” Animal reference, Dog/Cat objects
        Animal[] animals = { new Dog("Rex"), new Cat("Whiskers") };
        for (Animal a : animals) {
            System.out.println(a.name + " says " + a.sound()); // dynamic dispatch
        }

        // Abstraction โ€” Drawable reference
        Drawable[] drawables = { new Dog("Fido"), new Cat("Luna") };
        for (Drawable d : drawables) d.draw();
    }
}

Sources