Introduction
In the previous post, A complete guide to Java Optional, I explained about the Optional class and its use in Java. In this post, we will see the new methods added to the Optional class from Java 9 onwards (upto 11).
The new APIs summary
We will look at the following methods
- ifPresentOrElse()
- or()
- Stream()
- orElseThrow()
- isEmpty()
Execute an action if Optional is empty
API: public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
Added in: Java 9 - JDK-8071670
There is a method in the Optional class, isPresent, that takes a Consumer as a parameter. If the Optional is not empty, then the value is passed to the consumer. Thus, we can print the value of an Optional like
optional.ifPresent(System.out::println)
But, there is no way to perform an action (like printing) when the optional is empty. There existed two options.
Old way 1 - Using isPresent
We can use an if condition to check if the optional is empty or not and take appropriate action.
Optional<String> o1 = Optional.of("value");
Optional<String> o2 = Optional.empty();
if (o1.isPresent()) {
System.out.println("Found " + o1.get());
} else {
System.out.println("Not found");
}
This prints Found value
when using o1 and prints Not found
when using o2.
Old way 2 - Using orElseGet
This approach is uglier than the previous option. We can use a map and orElseGet. The map just returns the same value after printing (or any action that we want to do). Using orElseGet block, we print that the optional is empty and simply return a null. As mentioned earlier, I do not recommend this approach as it is very misleading.
o1.map(s -> {
System.out.println("Found " + s);
return s;
}).orElseGet(() -> {
System.out.println("Not found");
return null;
});
Java 9 added ifPresentOrElse method that would enable us in doing just this. It takes an additional Runnable parameter in addition to the Consumer that ifPresent takes.
o1.ifPresentOrElse(s -> System.out.println("Found " + s),
() -> System.out.println("Not found"));
Return an alternative (or default) Optional
API: Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
Added in: Java 9
Optional has orElse and orElseGet methods to return a default value when the Optional is empty. But, what if we need to return a default Optional? We have to wrap it in an Optional ourselves. Java 9 added a or method that allows us to pass a supplier that returns an Optional. An example will make this clear
Optional<String> o1 = Optional.of("value");
Optional<String> o2 = Optional.empty();
o1 = o1.or(() -> Optional.of("default"));
System.out.println(o1); //Optional[value]
o2 = o2.or(() -> Optional.of("default"));
System.out.println(o2); //Optional[default]
Create a Stream from an Optional
API: Stream<T> stream()
Added in: Java 9 - JDK-8050820
Before Java 9, there wasn’t a way to convert an Optional to an one element stream if the Optional is not empty or an empty stream if the Optional is empty.
Example:
Let us say we have a method that takes a string as an input and returns an Optional<String>. It returns the same input wrapped in an Optional if the passed string is a fruit; else it returns an empty Optional. It might look like
//Just a few fruits shown here
private static final List<String> FRUITS = Arrays.asList("apple", "mango", "banana", "peach");
private static Optional<String> transform(String s) {
return FRUITS.contains(s) ? Optional.of(s) :
Optional.empty();
}
Continuing on with the example, we start with a list of strings and our goal is to filter (pick) only the fruits and convert them to uppercase. We can do it like
List<String> input = Arrays.asList("apple", "mango", "banana");
input.stream()
.map(this::transform)
.filter(Optional::isPresent)
.map(Optional::get)
.map(String::toUpperCase)
.forEach(System.out::println);
It calls the transform method shown above to filter the input strings. Since, it returns an Optional, we have to again filter the ones that are not empty and map it (calling get on the Optional). Then, we convert it to uppercase to print it.
Now, since there is a stream method on the Optional, we can create a stream (of one or zero element) from an Optional. Next, we can use a flatMap instead of a map on the stream. Note: A flatMap on the Stream can be used when the mapper returns a stream itself i.e., it unwraps a Stream<Stream<T>> to Stream<T>.
input.stream()
.map(this::transform)
.flatMap(Optional::stream)
.map(String::toUpperCase)
.forEach(System.out::println);
This simplifies the processing to a large extent and makes the code less verbose.
This provides an exciting opportunity to replace all .filter(Optional::isPresent).map(Optional::get)
with .flatMap(Optional::stream)
.
Optional get method throws NoSuchElementException
New API: T orElseThrow()
Added in: Java 10 - JDK-8140281
The Optional.get method can throw a NoSuchElementException when called on an empty Optional. This is clearly documented in the Javadoc. But still, the developers carelessly use it without checking if the Optional is empty or not. This has to do with the IDE usage too - when auto-completion and method suggestions after entering a dot(.) shows the get method, we intend to choose it without reading the documentation. The method is named poorly is another argument.
Hence, a new API has been added that does just the work of the get method but named more appropriately - orElseThrow.
//Using o1 and o2 defined earlier
System.out.println(o1.orElseThrow()); //value
System.out.println(o2.orElseThrow()); //java.util.NoSuchElementException: No value present
Is the Optional empty?
New API: boolean isEmpty()
Added in: Java 11 - JDK-8184693
Similar to isPresent, a complementary API, isEmpty was added to Java 11. The argument for adding this is that the dichotomy would be similar to how we have Objects.isNull and Objects.nonNull.
//Using o1 and o2 defined earlier
System.out.println(o1.isEmpty()); //false
System.out.println(o2.isEmpty()); //true
Optional flatMap signature change
Changed in: Java 9
This is not a new API, but the change is significant enough (of importance) to be discussed.
The old signature (in Java 8) of flatMap method is
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
The new signature is
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
The difference is in the type of the value returned by the function. Before it was returning an Optional<U>; now, with this change, its type is ? extends Optional<? extends U>> .
Why is this significant? We can understand this with an example. Say, we have two classes Human and a Student. and the Student extends Human.
static class Human {
private String name;
public Human(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
static class Student extends Human {
Student(String name) {
super(name);
}
}
Let us say we have a method that maps a string to a Student. But, it returns an Optional<Student>.
private static Optional<Student> transformStringToStudent(String str) {
//Assume some case returns Optional.empty() too
return Optional.of(new Student(str));
}
We begin with an Optional<String> and want to map it to a Student. Since the mapping function returns an Optional in itself, using a map on the Optional would give us an Optional<Optional<Student>>. We can use a flatMap to flatten it.
Optional<String> input = Optional.of("some-name");
Optional<Student> studentResult = input.flatMap(this::transformStringToStudent);
//do something with the result
This would work fine with both Java 8 and Java 9.
What if change the left hand side to Optional<Human>?
Optional<Human> result = input.flatMap(this::transformStringToStudent);
It gives a compile time error with Java 8 but works in Java 9. It is because of the type of the function’s return.
Looking closer:
Function<? super T, ? extends Optional<? extends U>> mapper
The function can return an Optional of any subclass of U. Here (with the change), U is inferred as Human. Hence, the mapper can return an Optional<Student> as well. But in Java 8, the return type was just Optional<U> and hence we cannot return an Optional<Student> where we expect an Optional<Human>.
Conclusion
We looked at the new methods or APIs added to Java optional class from Java 9 through 11. We also saw the API change involving the flatMap method in the Optional class.