Java OOP Track
Learning Objectives
Use Java's object model: classes, packages and access, constructors, this/super, interfaces with default methods, abstract classes, records, enums and sealed classes.
Understand Object, equals/hashCode, generics with erasure, annotations, reflection, GC, exceptions and concurrency.
Prerequisites: Stages 1–2.
Intuition and Definition
Java is class-based with single inheritance of classes and multiple inheritance of interfaces. Every class extends Object. Objects live on the heap and are accessed through references; primitives (int, double) are values. Memory is garbage-collected.
public, protected (package + subclasses), package-private (default), private
All non-static, non-private, non-final instance methods are virtual
Abstract methods + default and static methods; multiple inheritance of interfaces
record (Java 16): immutable, generated equals/hashCode/toString
sealed interface Shape permits Circle, Square (Java 17) + pattern-matching switch (Java 21) Type erasure, bounded wildcards (PECS)
Checked (must be declared/caught) and unchecked exceptions; try-with-resources
== compares references; equals compares values; override hashCode with equals
Why It Matters
Java's OO model (and its JVM sibling Kotlin) dominates enterprise back ends and Android; its design choices — single class inheritance, interfaces, GC, checked exceptions — shaped C# and much OO teaching.
Syntax and Language Notes
The code in this module is shown for comparison and was not run (no JDK on the build machine). It uses Java 21 features.
// Java 21 (not run)
public sealed interface Shape permits Circle, Square {}
public record Circle(double r) implements Shape {}
public record Square(double side) implements Shape {}
public final class Geometry {
private Geometry() {} // utility class: no instances
public static double area(Shape s) {
return switch (s) { // exhaustive: compiler knows all permitted types
case Circle c -> Math.PI * c.r() * c.r();
case Square q -> q.side() * q.side();
};
}
}// equals/hashCode contract for an entity identified by id (not run)
public final class Vehicle {
private final String vin;
private int odometerKm;
public Vehicle(String vin) { this.vin = Objects.requireNonNull(vin); }
@Override public boolean equals(Object o) { return o instanceof Vehicle v && v.vin.equals(vin); }
@Override public int hashCode() { return vin.hashCode(); }
}// try-with-resources: AutoCloseable.close() runs even when an exception is thrown (not run)
try (var reader = Files.newBufferedReader(Path.of("trace.asc"))) {
reader.lines().filter(l -> l.contains("0C0")).forEach(System.out::println);
}Complete Example
A typical Java design — interface with a default method, abstract base class, records and an enum with behaviour (not run; read it alongside the C# version in Module 79, which was run):
// Java 21 (not run)
import java.util.*;
interface Notifier {
void send(String to, String text);
default void sendAll(List<String> to, String text) { to.forEach(t -> send(t, text)); } // default method
}
abstract class BaseNotifier implements Notifier {
private int sent; // encapsulated state
protected abstract void deliver(String to, String text); // template-method step
@Override public final void send(String to, String text) { deliver(to, text); sent++; }
public int sent() { return sent; }
}
final class EmailNotifier extends BaseNotifier {
@Override protected void deliver(String to, String text) { System.out.println("email " + to + ": " + text); }
}
enum Priority {
LOW(1), HIGH(3);
private final int weight;
Priority(int w) { weight = w; }
int weight() { return weight; }
}
record Alert(String vin, Priority priority) {
Alert { Objects.requireNonNull(vin); } // compact constructor for validation
}
public class Main {
public static void main(String[] args) {
var email = new EmailNotifier();
email.sendAll(List.of("[email protected]", "[email protected]"
…Step-by-Step Walkthrough
Notifier.sendAll is a default method — implementations inherit it without writing it.
BaseNotifier.send is final and calls the abstract deliver (Template Method); subclasses can't skip the counter.
Priority is a full class: enum constants carry data and behaviour.
Alert is a record: immutable fields, accessor methods (vin()), and value-based equals/hashCode/toString; the compact constructor validates.
Common Mistakes
Overriding equals without hashCode — breaks HashMap/HashSet.
Comparing strings or boxed numbers with == — compares references.
Raw types and unchecked casts — heap pollution under erasure.
Catching Exception broadly, or wrapping checked exceptions everywhere.
Mutable static fields and shared mutable state across threads without synchronization (volatile, synchronized, java.util.concurrent).
Calling overridable methods from constructors — the subclass override runs before its fields are initialized.
Trade-offs and Alternatives
Java favours clarity and tooling over expressiveness; records, sealed types and pattern matching modernised its data modelling. Kotlin (data classes, null safety) is a popular JVM alternative.
Real-World Use
Spring Boot services, Android apps, Hadoop/Kafka ecosystems, banking and telecom back ends, automotive back-office and diagnostics tools.
Practice
Q1. (Easy) Can a Java class extend two classes?
Answer: No — one class, but any number of interfaces.
Q2. (Medium) What does a record generate?
Answer: Private final fields, a canonical constructor, accessors, and equals, hashCode and toString based on all components.
Q3. (Advanced) Why can't you write new T() in a Java generic class?
Answer: Type erasure: T isn't known at run time. Pass a Supplier<T> or a Class<T> token instead.
Interview Question
Q1. "Explain the equals/hashCode contract." Answer: If a.equals(b) then a.hashCode() == b.hashCode(); equals must be reflexive, symmetric, transitive and consistent, and x.equals(null) is false. Hash-based collections first use hashCode to find a bucket, then equals; violating the contract makes objects "disappear" from sets/maps. Base equality on immutable fields (an entity's id, or all fields for value types/records).
Quick Quiz
Q1. Default access level in Java?
Answer: Package-private.
Q2. Are Java instance methods virtual by default?
Answer: Yes (unless static, private or final).
Q3. What makes switch over a sealed interface exhaustive?
Answer: The compiler knows all permitted subtypes, so it can check every case is handled.
Summary
Java: single class inheritance + interfaces, references and GC, records, enums, sealed types, erasure-based generics, checked exceptions.
Related: Modules 7, 12, 17, 82.