null and NullPointerException

In the case where createUnitCircle returns null, this creates a NullPointerException error.

The core problem: when createUnitCircle returns null, you can’t call .contains(Point) on it — you get a NullPointerException. You can do a null check before calling the next function. But that doesn’t scale well. Imagine if every single caller of createUnitCircle has to remember to do this check. If one caller forgets, you get a runtime NPE.


Optional Class

A box that either has something inside or is empty. You return Optional<Circle> which tells the caller you might get a Circle, or you might not.

3 ways to create one:

  • Optional.of(circle) — wraps a real Circle in the box. (Throws an exception if you try to pass null here, so it’s safe.)

  • Optional.empty() — an empty box, explicitly no Circle. This is returned instead of return null.

  • Optional.ofNullable(value) — if the value is null, gives you an empty box; otherwise wraps it. This is the bridge between old null-returning code and Optional.

When using .contains() You can’t call .contains() directly on an Optional<Circle> as it’s a box that might contain one so you have to unwrap it first.

createUnitCircle(p, q) 
	.map(c -> c.contains(point)) 
	.orElse(false);

The difference with null checks: Writing null checks for each function is optional. We may forget. Writing code to unwrap Optional objects is required for all. You always have to unwrap it somehow and every unwrapping method makes you deal with the empty case.


Returning an Optional

For methods that might not have a value to return, instead of returning null, return an Optional. If a method always gives you a valid result, it should just result the normal type.

Optional<Circle> createUnitCircle(Point p, Point q) {
	double d = p.distanceTo(q);
	if (d < EPSILON || d > 2.0 + EPSILON) {
		return Optional.<Circle>empty();
	}
	
	Point m = p.midPoint(q);
	double mp = Math.sqrt(1.0 - Math.pow(p.distanceTo(m), 2.0));
	double theta = p.angleTo(q);
	m = m.moveTo(theta + Math.PI / 2.0, mp);
	return Optional.<Circle>of(new Circle(m, 1.0));
}

Chaining with Optional

When dealing with optional, chaining changes. Unwrapping isn’t done in createUnitCircle or contains method.

createUnitCircle(p, q).contains(point)  // ❌ won't compile

// If the first one returns optional
// This is called cross-barrier manipulation: pass contains method into Optional via a higher-order function
createUnitCircle(p, q).map(c -> c.contains(point)) // ✅ 

// If both returns optional
createUnitCircle(p, q)
    .map(c -> findClosestPoint(c))  // Optional<Optional<Point>> 😬
createUnitCircle(p, q) .flatMap(c -> findClosestPoint(c)) // Optional<Point> ✅

Computation Context

A box that handles the messy stuff (like what if there’s no value?) e.g. Optional is a computation context that handles invalid or missing values.

A computation context is any “box” that:

  1. Wraps a value — like Optional.of(1) puts the integer inside the box
  2. Lets you pass functions in via higher-order methods like .map() — so the function operates on the value inside the box, without you ever taking it out

Building my own Maybe Context

A context with connotations of maybe a T or maybe empty

Requirements

  • of and empty — the factory methods to create a Maybe with a value or without one (same as Optional.of() and Optional.empty())
  • private helper methods — probably things like checking whether the Maybe contains a value or is empty
  • toString — so it prints nicely like Maybe[value] or Maybe.empty

of and empty Methods

of and empty are factory methods — they create Maybe instances for you. Maybe.of(123) creates a Maybe with 123 inside the box. Put something in a box. Maybe.empty() creates a Maybe with nothing inside. It’s an empty container.

Overriding equals Method in Maybe

And how do we compare equality between two maybe values:

@Override
public boolean equals(Object obj) {
	if (this == obj) {
		return true;
	}
	if (obj instanceof Maybe<?> other) {
		if (this.value == null && other.value == null) { // both empty
		    return true;
		}
		if (this.value == null || other.value == null) { // one is empty
		    return false;
		}
		return this.value.equals(other.value);	// check the wrapped value
	}
	return false;
}

Higher order functions

filter — keeps the value only if it passes a test, otherwise becomes empty:

  • empty().filter(even) → stays Maybe.empty
  • of(2).filter(even) → 2 is even, so Maybe[2]

ifPresent — runs a side effect (like printing) only if there’s a value:

  • empty().ifPresent(print) → nothing happens
  • of(123).ifPresent(print) → prints 123

map — transforms the value inside:

  • empty().map(x -> x + 1) → stays Maybe.empty
  • of(123).map(x -> x + 1)Maybe[124]

All together of(123).filter(even).map(x -> x + 1).orElse(-1) 123 is odd so filter makes it empty, map skips, orElse gives the default: -1.

Why we need flatMap

Maybe.of(2).map(g) Maybe[Maybe[3]] Maybe.of(2).flatMap(g) Maybe[3]

jshell> Maybe<Integer> oi = Maybe.of(1)
oi ==> Maybe[1]

jshell> Maybe<? extends Number> on = oi
on ==> Maybe[1]

jshell> List<Maybe<Integer>> lmi = List.of(oi)
ooi ==> [Maybe[1]]

jshell> List<Maybe<? extends Number>> lmn = lmi // ??
jshell> List<? extends Maybe<Number>> lmn = lmi // ??

List<Maybe<? extends Number>> can hold Maybe<Integer>, Maybe<Double>, etc., so assigning lmi (which is List<Maybe<Integer>>) to lmn works. ✅

Generics are invariant: (relationship) does not change Maybe<Integer> is NOT a subtype of Maybe<Number>, even though Integer extends Number. List<? extends Maybe<Number>> lmn = lmi → doesn’t work.


Eager vs Lazy evaluation

orElse(value) orElseGet(supplier)

orElse(foo()): Java evaluates arguments before passing them to a method. foo() is evaluated to get a result and passes it to orElse. orElse activates when needed.

s = () foo() and orElseGet calls get() orElseGet(s): s is a reference to a Supplier object. Nothing gets called. orElseGet only calls s.get() when Maybe is empty.

Why doesn’t s get evaluated like orElse(foo())? s itself is a variable (or a value), so Java just passes the reference. foo() is a function call, so Java needs to execute it to get the value

Declaratively / Imperative

Imperative: You tell the computer how do it Declarative: You tell the computer what you want. You’re saying: “map over the circle, count contained points, default to 0 if empty.” You never manually check isPresent(), never pull values out with get(), never write a loop. Don’t use optional imperatively.