Progress
🌍
Why does this topic matter?
Object-Oriented Programming (OOP) is how engineers manage complexity. A single FAANG backend service can have millions of lines of code written by hundreds of engineers. Without OOP principles, that codebase collapses under its own weight. Encapsulation, Inheritance, Polymorphism, and Abstraction are the four tools that keep large systems maintainable — and they appear directly in interview questions like "Design a Parking Lot" or "Design an LRU Cache."

📖 Before We Start — The Big Picture

In the real world, we manage complexity by grouping related things. A class is a blueprint (the architect's drawing of a house). An object is the actual thing built from that blueprint (a real house). Every Toyota car on the road is a different object, but they all share the same class (blueprint). OOP is just this idea — applied to code.

You already used objects in Lecture 2 when you read about the String class, ArrayList, and the JVM's heap. Every single one of those is a class. This lecture teaches you how to design your own classes and how to make them work together elegantly.

🔗
How it connects: Lecture 2 explained memory — every object you new up goes on the heap. This lecture teaches you what those objects are. Lecture 4 (Java 8+) extends this with lambdas and functional interfaces, which are just anonymous object implementations behind the scenes.

🏗 The Four Pillars of OOP

Pillar What it means DSA Example Key mechanism
Encapsulation Bundle data + methods; hide internals via access modifiers LinkedList: private head, public add/remove private fields + public getters/setters
Abstraction Expose what to do; hide how PriorityQueue: you call offer/poll, not heapifyUp abstract classes, interfaces
Inheritance Subclass reuses + extends superclass TreeNode extends Node; DFS methods inherited extends keyword, super()
Polymorphism Same interface, different behaviour per type Comparator: sort by salary vs name without changing sort logic @Override, method overloading

1. Encapsulation

☕ Java · BankAccount
class BankAccount {
    private double balance;  // hidden — cannot access directly
    BankAccount(double init) { balance = init; }

    public void deposit(double amt) {
        if (amt <= 0) throw new IllegalArgumentException("Amount must be positive");
        balance += amt;
    }
    public double getBalance() { return balance; } // read-only access
}
// Cannot do: account.balance = -1000; (compile error!)
// Must go through deposit/withdraw which enforce business rules

2. Inheritance

☕ Java · Inheritance Chain
abstract class Shape {
    abstract double area();
    void print() { System.out.println("Area: " + area()); } // inherited
}

class Circle extends Shape {
    double r;
    Circle(double r) { this.r = r; }
    @Override double area() { return Math.PI * r * r; }
}

class Rectangle extends Shape {
    double w, h;
    Rectangle(double w, double h) { this.w=w; this.h=h; }
    @Override double area() { return w * h; }
}

// Polymorphism in action:
Shape[] shapes = {new Circle(5), new Rectangle(3,4)};
for (Shape s : shapes) s.print(); // calls correct area() per type

3. Polymorphism — Runtime vs Compile-time

Type When resolved Mechanism Example
Runtime (Dynamic) At runtime @Override — subclass overrides parent method Shape.area() calls Circle.area() or Rectangle.area()
Compile-time (Static) At compile time Method overloading — same name, different params print(int) vs print(String) vs print(double)

4. Abstract Class vs Interface — The Critical Comparison

Dimension Abstract Class Interface
Instantiation Cannot instantiate Cannot instantiate
Constructor ✅ Can have ❌ Cannot have
Fields Any access modifier Only public static final (constants)
Methods Abstract + concrete Abstract + default + static (Java 8+)
Multiple inheritance ❌ One parent only ✅ A class can implement many
When to use "Is-a" with shared state/code (Template Method pattern) "Can-do" (Comparable, Runnable, Serializable)
DSA Example AbstractList providing default iterator Comparable for natural ordering in TreeSet/PQ

📊 Comparable vs Comparator

☕ Java · Both Patterns
class Employee implements Comparable<Employee> {
    String name; int salary;
    @Override
    public int compareTo(Employee o) {
        return Integer.compare(this.salary, o.salary); // natural order = by salary
    }
}

// Comparable: used when ONE natural ordering makes sense
// TreeSet<Employee> automatically uses compareTo()

// Comparator: used when you need MULTIPLE orderings or can't modify class
Comparator<Employee> byName = Comparator.comparing(e -> e.name);
Comparator<Employee> bySalaryDesc = (a,b) -> b.salary - a.salary;

// Chaining comparators (sort by salary desc, then by name asc)
Comparator<Employee> combined =
    Comparator.comparingInt((Employee e) -> e.salary).reversed()
              .thenComparing(e -> e.name);

list.sort(combined);
Arrays.sort(arr, combined);
new PriorityQueue<>(combined); // custom-ordered heap

🧬 Generics Deep Dive

☕ Java · Generic Methods & Bounded Wildcards
// Generic method — works for any Comparable type
public static <T extends Comparable<T>> T findMax(List<T> list) {
    T max = list.get(0);
    for (T item : list) if (item.compareTo(max) > 0) max = item;
    return max;
}

// Bounded wildcards
// ? extends T — can READ (covariant): upper bound
double sumOfList(List<? extends Number> list) {
    double sum = 0;
    for (Number n : list) sum += n.doubleValue();
    return sum;
} // works for List<Integer>, List<Double>, List<Long>

// ? super T — can WRITE (contravariant): lower bound
void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) list.add(i);
} // works for List<Integer>, List<Number>, List<Object>

// PECS: Producer Extends, Consumer Super

📋 ArrayList Internals

📌
ArrayList is backed by a plain Object[] array. When you call new ArrayList<>() it creates an Object[10] internally. When full, it allocates a NEW array of size 1.5× and copies everything over. This copy is O(n) but happens rarely, so add() is O(1) amortised.
☕ Java · ArrayList resize simulation
// Pseudocode approximation of ArrayList internals
class SimpleArrayList<T> {
    Object[] data = new Object[10]; // default capacity
    int size = 0;

    void add(T item) {
        if (size == data.length) {               // capacity hit
            int newCap = (size * 3) / 2 + 1;      // ~1.5× growth
            data = Arrays.copyOf(data, newCap);   // O(n) copy!
        }
        data[size++] = item;                      // O(1)
    }

    void remove(int idx) {                       // O(n) — shifts elements left
        System.arraycopy(data, idx+1, data, idx, size-idx-1);
        data[--size] = null;                   // prevent memory leak
    }
}
// Tip: new ArrayList<>(expectedSize) avoids all resizing overhead

💪 Practice Problems

Problem 01 · Design
Design a Stack Using ArrayList
EasyAmazonOOP Design
Problem 02 · Sorting
Sort Employees by Salary then Name
MediumMicrosoftComparator Chain
Problem 03 · HashMap Grouping
Group Anagrams
MediumAmazonGoogleHashMap
Problem 04 · Two-Stack OOP
Implement Queue Using Two Stacks
EasyAmazonMicrosoftOOP Design
Problem 05 · Polymorphism
Iterator Pattern — Reverse Iterator
MediumGoogleIterator / Interface

📝 Assignment

📋
Lecture 3 Assignment
20+ company-tagged OOP & collections problems including LRU Cache, Design HashMap, Group Anagrams, and inheritance design exercises.

📄 Open Assignment →

✅ Lecture Completion Checklist

I can name all 4 OOP pillars and give a DSA example for each
I know when to use Abstract Class vs Interface (5+ criteria)
I can implement a custom Comparator using lambda and Comparator.comparing()
I understand Comparable for natural ordering (TreeSet/PriorityQueue)
I know why ArrayList.remove(int index) is O(n) but get(index) is O(1)
I can write a generic method with bounded type parameters
I can explain what PECS (Producer Extends, Consumer Super) means
I understand ArrayList resizing: when it happens and what the new capacity is
I can solve Group Anagrams using HashMap in under 5 minutes
I can explain the difference between method overriding and overloading
🧠
You're ready for Topic 4: Java 8+ & Streams
You've nailed OOP — inheritance, polymorphism, and Java Collections. Java 8 brought the biggest syntax revolution in the language: lambdas, streams, and Optional. Master these and your code becomes concise and interview-ready.
← Topic 2: Java MemoryTopic 4: Java 8+ & Streams →