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 passnullhere, 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 isnull, 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:
- Wraps a value — like
Optional.of(1)puts the integer inside the box - 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
ofandempty— the factory methods to create a Maybe with a value or without one (same asOptional.of()andOptional.empty())- private helper methods — probably things like checking whether the Maybe contains a value or is empty
toString— so it prints nicely likeMaybe[value]orMaybe.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)→ staysMaybe.emptyof(2).filter(even)→ 2 is even, soMaybe[2]
ifPresent — runs a side effect (like printing) only if there’s a value:
empty().ifPresent(print)→ nothing happensof(123).ifPresent(print)→ prints123
map — transforms the value inside:
empty().map(x -> x + 1)→ staysMaybe.emptyof(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.
