Quite long ago around the year 2014 Java 8 was released bringing the new standard to the Java ecosystem. One of the features included in this release was an Optional. Simple yet very useful class for handling nullable objects and values.
Quick time skip to year 2023 and Optional, consists of approx. 500 lines of code and 20 methods with a public access level (including hashCode, equals, toString, and Javadocs) with various possibilities and is widely used throughout the Java ecosystem.
But how it was changing through the time, what was added and what deleted, how the Optional evolution looked like ?
Answering this question is the main topic of today’s article.
Why Does Optional Exist in Java?
Many modern-day programming languages have is a feature (or a bug, depending on what you think) called “the billion-dollar mistake” or a null pointer reference if you prefer — basically the pointer containing reference to invalid object. Such an approach makes occurrences of exceptions like NullPointerException possible.
The NullPointerException is one of the most common if not the most common runtime exceptions. It tends to appear in the most unexpected and unwanted places of our code base causing a great deal of trouble to us and our customers.
Of course different languages adopted different approaches for handling this problem. So have special operators that allow access to a property in a null-safe way like ? in Typescript. Others like Kotlin does not allow null pointers to occur on language level.
Optional vs If
private String getAssignManagerName(Task task) { if (task != null) { Team team = task.getAssignTeam(); if (team != null) { Manager manager = team.getManager(); if (manager != null) { return manager.getName(); } } } throw new UnableToGetManagerNameException("Unable to get assign manager name"); }
As you can see this code is fairly complex and long. We have to make each non-null check explicitly and in a separate statement so we ended up with a lot of boilerplate.
This issue was one of the main reasons behind the introduction of Optional to Java. Below you can see the same statement but refactored with the use of Optional.
private String getAssignUserFirstName(Task task) { return Optional.ofNullable(task) .map(Task::getAssignTeam) .map(Team::getManager) .map(Manager::getName) .orElseThrow(() -> new UnableToGetManagerNameException("Unable to get assign manager name")); }
As you can see here, almost everything is done for us. First, we wrap our task with Optional. Then, we execute a sequence of map operations on it.
Finally, if our Optional is empty, or we weren’t able to get value at some point, we throw an exception with the help of orElseThrow. There is no boilerplate code here and the example is less wide than the if based one.
I leave it up to you to decide which one is more readable and meaningful for you.
Optional Evolution Since Java 8
Optional was introduce to Java ecosystem besides methods like map, orElseThrow and ofNullable it contains other useful methods:
of(T value)
, which is similar toofNullable(T value)
but should only be used when you want to create optional from non-null values.flatMap(Function<? super T,Optional> mapper)
If value inside Optional is present performs operations returning Optional and handle flattening nested Optionals the results to single Optional. If Optional is empty does nothing.filter(Predicate<? super T> predicate)
If value inside Optional is present check if it matches certain predicate. If Optional is empty does nothing.ifPresent(Consumer<? super T> consumer)
Takes Consumer interface and invokes it when value in Optional is present otherwise it does nothing. Very useful for any type of side effects actions as it returns void.
I purposefully did not describe all methods from Java 8 Optional as this article should focus mainly on changes in the API, not on how it originally looked like. If you wish to get more familiar with all the methods check the link to the official Oracle documentation.
Java 9
This version brings the highest number of new methods to our API, namely:
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
If the value is present it will apply a Consumer action to it, otherwise it performs a supplied runnable action. Similar in meaning to original ifPresent but also allows to handle empty Optionalsor(Supplier<? extends Optional<? extends T>> supplier)
If the value is not present this method will return Optional provided by supplier function. We can further operate on this value in subsequent stepsstream()
If our value is present it converts our Optional into Stream containing that value. If not, we get an empty Stream. Such a conversion gives us access to all methods declared in Stream API which is more powerful and provides more functionalities than the Optional API.
Java 10
In this version, Oracle decided to add only one new method to Optional class. On the one hand, it seems to be quite useful but on the other — not so much. I will describe it and leave it to you to rate how useful it can be.
orElseThrow()
Have the same meaning as original orElseThrow but the exception which will be thrown is defined for us so we do not have to specify it. It will always throwNoSuchElementException
.
Java 11
In Java 11 we get only one new method, again. Which, in my opinion, is not very useful.
isEmpty()
It works opposite to isPresent() from Java 8 — it returns True if value is not present in our Optional instance.
Java 12 and Above
Unfortunately, there were no updates in Optional API since Java 12. If such changes appear, this article will be updated.
Closing Thoughts
Through 3 Java releases, Optional got 6 new methods which is approx. It translates to 25% of all methods in Optional class. With them, we got new features and possible use cases.
I hope that this article gave you a better understanding of how Optional API was changing with subsequent Java releases and why it could be better to use Java 9 or 11 over Java 8.
Thank you for your time.
Comments are closed.