Skip to content
Part IA Michaelmas Term

Optional and Pattern Summary

java.util.Optional<T> is a container object that either holds a non-null value or is empty — an explicit, type-checked alternative to using null to mean “no value”.

The problems with null

Error-prone: It is easy to forget a null-check, leading to NullPointerException at runtime. The compiler does not help — any reference can be null at any time.

Verbose: Defensive code is littered with null-checks:

if (x != null) {
    if (x.getY() != null) {
        return x.getY().getZ();
    }
}
return null;

No semantic meaning: A null String could mean “absent”, “not yet loaded”, “an error occurred”, or “the developer forgot to initialise it”. The null itself does not tell you why it is null.

How Optional solves these

Optional forces the caller to actively acknowledge the possibility of absence and handle it:

Optional<String> maybeName = findName(id);

// Old style (check-then-act — still possible, but the Optional signals absence)
if (maybeName.isPresent()) {
    System.out.println(maybeName.get());
}

// Functional style (preferred — declarative, no if-checks)
maybeName.ifPresent(name -> System.out.println(name));

// Provide a default
String name = maybeName.orElse("Unknown");

// Lazy default (Supplier — only evaluated if needed)
String name = maybeName.orElseGet(() -> computeFallbackName());

// Throw if absent
String name = maybeName.orElseThrow(() -> new NotFoundException("No name for " + id));

Optional’s functional API

// Transform the value if present, producing a new Optional
Optional<String> upper = maybeName.map(String::toUpperCase);

// Transform and flatten (when the mapping function returns Optional)
Optional<Integer> length = maybeName.flatMap(n -> Optional.of(n.length()));

// Filter by predicate — empty if present but does not match
Optional<String> longName = maybeName.filter(n -> n.length() > 5);

When to use Optional (and when NOT to)

Designed for: Method return values that may legitimately be absent. For example, finding an entity by ID in a database — the entity might not exist.

NOT intended for:

MisuseWhy it is wrong
FieldsOptional is not Serializable; adds boxing overhead; the absence signal should be in the method return
Method parametersForces callers to wrap values; awkward at call sites
Collection contentsAn empty collection already means “no elements”; do not wrap List<Optional<T>>
Replacing all nullsNull still has performance advantages for internal code; Optional is for public API boundaries

Optional was introduced primarily to make API contracts explicit — when a method returns Optional<T>, the caller knows the value may be absent and the type system enforces handling that case.

Pattern summary table

PatternIntentKey structurePrinciple
CompositeTreat one object and a group of objects uniformlyInterface → Leaf & Composite both implement it; Composite holds List<Interface>OCP
DecoratorAdd behaviour to an individual object dynamicallyInterface → Base impl & Wrapper both implement it; Wrapper holds one Interface (inner)OCP
StateObject changes behaviour when its internal state changesContext holds State reference; state transitions from within the objectSRP, OCP
StrategyClient swaps algorithm externally at runtimeContext holds Strategy reference; strategy chosen by clientOCP
SingletonExactly one instance with global accessPrivate constructor + static factory method(Often criticised)
ObserverNotify many dependents automatically on state changeSubject holds List<Observer>; Observer interface with update() methodLoose coupling, OCP

How to tell patterns apart in a Tripos scenario

  1. “Treat one and group uniformly” → Composite.
  2. “Add behaviour to one object, wrap/decorate it” → Decorator.
  3. “Behaviour changes based on internal state / lifecycle” → State.
  4. “Choose between interchangeable algorithms / ways of doing something” → Strategy.
  5. “Exactly one instance of this must exist” → Singleton.
  6. “Notify many things when something changes” → Observer.

Critical Tripos answer structure

For any pattern question:

  1. State the problem the scenario faces (what would go wrong without the pattern).
  2. Name the pattern explicitly.
  3. Sketch the participants — which interfaces, which concrete classes, what relationships exist between them.
  4. Write the code — adapt the pattern to the scenario’s specific domain, using the scenario’s actual class and method names.
  5. Justify the choice — tie it to OCP, loose coupling, or SRP. Explain what would have to change if a new requirement arrived and why the pattern makes that change trivial.

Never just describe a pattern in abstract terms — always ground it in the specific classes and methods from the question.