Optional - nus-cs2030/2122-s1 GitHub Wiki

How I Learned to Stop Worrying about null and Love the Optional

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

Optional orElse() vs orElseGet()

  • orElse() will call the function inside of it regardless of whether the Optional.isPresent == true.
  • orELseGet() will only call the function inside of it if Optional.isPresent == false. If Optional.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

Optional map vs Optional flatMap

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
    }
}

When to use flatMap and map

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");
    }
}
⚠️ **GitHub.com Fallback** ⚠️