Optional - nus-cs2030/2122-s1 GitHub Wiki
Optional is java's way to abstract away null checks as java is not null safe by default unlike other languages like dart and rust.
Useful | Breaks Tell-Don't-Ask |
---|---|
ifPresent | get |
ifPresentOrElse | isPresent |
map | isEmpty |
orElse | |
orElseGet | |
stream | |
flatMap |
How to make: Optional.<T>of(object)
or Optional.empty()
Optional.<Circle>of(new Circle(new Point(0, 0), 1))
Optional.empty()
// use this if object passed in might be null, to get empty Optional in that case instead of NullPointerException
Optional.<Integer>ofNullable(null) // Optional contains Optional.empty()
// by default, Optionals doesn't let you put in null values unless explicitly stated
Optional.<Integer>of(null) // throws NullPointerException
-
orElse()
will call the function inside of it regardless of whether theOptional.isPresent == true
. -
orELseGet()
will only call the function inside of it ifOptional.isPresent == false
. IfOptional.isPresent == true
, then no function will be called
Both methods are very similar. However, its best if you use orElseGet()
to avoid calling expensive functions unnecessarily. For example:
// countToOneBillion() takes the startingNumber and counts to a billion
// As you can see, countToOneBillion() is a very expensive operation, and should only be called if needed
Optional<Integer> startingNumber = Optional.<Integer>ofNullable(1);
startingNumber.isPresent(); // true
startingNumber.orElse(countToOneBillion()); // even though the optional has a value, we still count to a billion, which not what we want
startingNumber.orElseGet(() -> countToOneBillion()); // we do not count to a billion as the Optional has a value
startingNumber = Optional.<Integer>ofNullable(null);
startingNumber.orElse(countToOneBillion()); // since the Optional.isEmpty, we count to one billion
startingNumber.orElseGet(() -> countToOneBillion());
This is an example of "caching" as mentioned in Lecture 9 on Lazy Evaluation
Ah the age of question, what's the difference between Optional.flatMap
vs Optional.map
and when should I use which?
In the most concise way possible, an Optional.map
can return a null value if there is no value inside the Optional. This is because the Optional.map
is meant to take in a function that does not return an Optional. So what does this mean for the programmer using Optional.map
? It means that since we do not know whether our mapper function will return a null value (and cause misery for everyone), we must wrap the result of the map in an Optional to return an Optional.empty
if there is a null value (always use protection).
On the other hand, Optional.flatMap
will automatically make sure that it will return an Optional.empty
if there is no value inside the Optional. This means Optional.flatMap
must take in a function that returns an Optional or else it will not compile. Thus, this means that there is no need to wrap the result of the Optional in another Optional as it's redundant and it means that flatMaps will use protection by default (you won't encounter null values).
To further illustrate whats going on, lets take a look at the source code for Optionals
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent) {
return empty();
} else {
return Optional.ofNullable(mapper.apply(value)); // .ofNullable means that the Optional can have null values inside of it
// Note: this means that the program will silently continue working incorrectly and not throw a NullPointerException
}
}
public <U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) { // function must return an Optional type
Objects.requireNonNull(mapper);
if (!isPresent) {
return empty();
} else {
return Objects.requireNonNull(mapper.apply(value)); // .requireNonNull means that the Optional cannot have null values or else it will throw an angry compile error
}
}
While there are many scenarios where you would use map and flatMap and its impossible to cover them all, here are some examples.
Pertaining to Lab 5 on KeyableMaps, we could change out KeyableMap's get method to return an Optional<U>
as we want to have null values when searching for a student's details in a roster (see level 6 of Lab 5, where we must return "No Such Records"). As such can using the "getGrade" method on an optional to get the grade from a deeply nested HashMap/roster, we would first we flatMaps to catch null values and turn them into Optional.empty, as only Assessment.getGrade()
can determine whether the student and their module exists in the roster.
public class KeyableMap<V extends Keyable> implements Keyable {
private final Map<String, V> dict;
public Optional<V> get(String key) {
return Optional.ofNullable(dict.get(key))
}
}
public class Roster extends KeyableMap<Student> {
public String getGrade(String ... input) {
String student = input[0];
String module = input[1];
String assessment = input[2];
return super.get(student).
flatMap((student) -> student.get(module)). // return empty Optional or Optional<Module> as we cannot use .get() on null
flatMap((module) -> module.get(assessment)). // return empty Optional or Optional<Assessment> as we cannot use .get() on null
map((assessment) -> assessment.getGrade()). // if null then return "No such record", if not null then return using getGrade()
orElse("No such record");
}
}