Skip to main content

Coding with Java Optionals!

In the previous blog NullPointerException to Optional! , we discussed how Optional came to the world. In this blog, let’s look at its usage/misusage.

Optional has been a very controversial java-type Not just the incorrect use, often times you see a lot of unnecessary usage and silent nulls.

Optional<T> indicates the presence or absence of a value of type T
The java.util.Optional class is decorated with 3 static factory methods. Any of these can be used to create an instance of Optional.

public static <T> Optional<T> empty     () 
public static <T> Optional<T> of (T value)
public static <T> Optional<T> ofNullable(T value)

Once you have the instance, the following are some (not all) significant methods available for use

public T           get       () 
public boolean isPresent ()
public void ifPresent (Consumer<? super T> consumer)
public Optional<T> filter (Predicate<? super T> predicate) <U> Optional<U> map (Function<? super T, ? extends U> mapper)
<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
public T orElse(T other)
public T orElseGet(Supplier<? extends T> other)
public <X extends Throwable> T orElseThrow

Here are some of my recommendations for using Optional

1. Use Optional.empty()

Never return a null from a method with returns type as Optional<T>, instead, return Optional.empty()

ExplanationThe whole reason for using Optional is, to have a wrapper over the absence or presence of a value. In some cases, the value can be absent (null) but the wrapper should always be present. Optional.empty() returns a wrapper with an absent( null) value.

2. Do not overuse Optional

Before we talk about this point, let’s say, we should avoid returning nullable references in any new code. However, you will still face some (usually a lot) well-written library code or existing code that can successfully consume nullable references. In that case, it’s ok to return null, make sure the return type is not an Optional.

Now about Optional overuse.
As I said in earlier post, Optional is not a replacement for null, it’s just a tool with 16 bytes overhead, to help us write fluent code API with method chaining. I’ve seen the following type of code a lot more than you can imagine.

Car car = getCarFromSomewhere(); 
Optional<Car> carOptional = Optional.ofNullable(car);
return carOptional.isPresent()
? carOptional.get().getMake();
: null ;

It is a working code but has a lot of unnecessary lines & Object allocation. 
Avoid such code clutter by a simple null check

Car car = getCarFromSomewhere();
return (car != null)
? car.getMake()
: null ;

3. Be cautious using orElse() with multiple map()

If you really want to write a fluent pipeline for the above example, the below code will work well and test cases will also pass.

//nicest looking code for above example but read explanation 
return Optional
.ofNullable(getCarFromSomewhere())
.map(Car::getMake)
.orElse(null);

But this code is doing more than what we assumed. For both null equalities (either car or make) it fallbacks to the same value from orElse().

The above code works well when you return null, but if you need to return different values for null equality of car vs make, a clean pipeline is not an option

4. Use ofNullable() insteadof of()

of(value) is a factory method to create the Optional wrapper of known non-null value. Use it only when you are defaulting to some known value in your method returns. In all other cases go with ofNullable

//Incorrect, It can through NullPointerException (NPE)
return Optional
.of(getCarFromSomewhere()) //use ofNullable() instead
.map(Car::getMake)
.orElse(null));

of()method use Objects.requireNonNull(value) for setting the value in Optional wrapper. which will throw NPE when value is null.

5. Don't call get() without verification

Never call get()on an Optional instance without verifying the presence of value. No one makes this mistake anymore, if you see someone doing it, it's time for a serious talk.
Let's assume someone changed the return type forgetCarFromSomewhere() and now it returns an Optional<Car>.

private Optional<Car> getCarFromSomewhere(){
…..
}

Let's look at 3 code blocks in some method which is consuming /calling getCarFromSomewhere()

//Wrong codeOptional<Car> carOptional = getCarFromSomewhere(); 
return carOptional.get(); // can throw NoSuchElementException

It can be written like below

//non error-prone but still cluttery codeOptional<Car> carOptional = getCarFromSomewhere();
return carOptional.isPresent()
? carOptional.get();
: null ;

The best way is, to change the return type of your consuming method so it can return an Optional instead of a nullable reference, but in the cases when you have to return nullable references, the below code is cleaner

return getCarFromSomewhere()
.orElse(null));

6. Use orElseGet() not orElse() for unconstructed fallback values

As we saw in the above code examples, it's better to use orElse()instead of isPresentget pair. But the caveat is, the code present in orElse()is always evaluated even when Optional is not empty.

So if you always use orElse() for Optional.empty scenarios, then

  • In the worst cases, when you call a state-altering code in orElse(), States will be changed even for non-empty Optionals. (Bad Practice)
  • In best cases, it will be a performance hit as the code in orElse will still be executed

Nevertheless, An easy to remember rule is Don't use orElse() if you don’t have an already constructed object to return.

Explanation:

return getCarFromSomewhere()
.map(Car::getMake)
.orElse(getDefaultMake());
private Make getDefaultMake(){
// increment defaultMakeRequested counter by one
}

Even if getCarFromSomewhere() always returns a non-empty Optional<Car> and all of the Car always contain a non-null Make, we will still have defaultMakeRequested increased by one, for every car.

The solution is
Use orElseGet() for unconstructed fallback responses.
It accepts a Supplier which gets executed only when the Optional chain faces a non-empty Optional. So our code should look like

return getCarFromSomewhere()
.map(Car::getMake)
.orElseGet(this::getDefaultMake);

7. Use dedicated Optional container for primitive type

Whenever you want Optional of primitive types int, long & double, prefer to use dedicated primitive Optional wrappers OptionalIntOptionalLong & OptionalDouble. 
Try to avoid using Optional<Integer>Optional<Long> & Optional<Double>.

//Avoid 
Optional<Integer> value = Optional.of(1);
Optional<Long> value = Optional.of(1L);
Optional<Double> value = Optional.of(1.1d);
// Use
OptionalInt value = OptionalInt.of(1);
OptionalLong value = OptionalLong.of(1L);
OptionalDouble value = OptionalDouble.of(1.1d);

Explanation
If you do not use dedicated wrappers (the first way in the above example), the runtime needs to perform autoboxing, which induces its own performance penalties.

Above are well-proven and widely accepted recommendations, But I do have some debatable recommendations, which I’ll be covering in my next blog.

Comments

Popular posts from this blog

An Introduction to Quartz Scheduler

It's a common use case to have an enterprise application, perform specific work, at a specific time or in response to a specific action. In other words, “There is an ask to execute a  Job  upon a predefined  Trigger ”. This brings us to the need for a  Scheduling System.  A system, where  Jobs  &  Trigger  can be registered and the system will manage the remaining complexity. Thankfully for the Java systems,  Quartz  is for rescue. It‘s an open-source library that has been extensively used in enterprise applications for more than a decade. Components in Quartz Sub System: Following are the all major component in the Quartz subsystem: Scheduler : It’s the control room of Quartz. It maintains everything required for scheduling,  such as managing listeners ,  scheduling jobs , clustering, transactions & job persistence. It maintains a registry of  JobDetails ,  Listeners  &  Triggers , and executes Job & Listeners when their associated Trigger is fired. SchedulerFactor

Unable to Redo in VS-Code & Intellij

Since the beginning of personal computers, few keyboard shortcuts are common among all operating systems and software. The ubiquitous cmd+c (copy), cmd+v(paste) , cmd+z (undo) and cmd+y (redo) I am not sure why, both of my favorite IDEs,  Visual Studio Code  &  Intellij  decided to not use  cmd+Y for redo.Below are the quick steps to configure  cmd+Y for a redo in VS-Code & Intellij Visual Studio Code Open VS Code & Go to keyboard shortcuts There will be a search bar at the top Type “  redo  “ in the search bar. You can see on my system its still mapped to  shift+cmd+z Double click on  ⇧ ⌘ z  and the below box will appear. Do not click anywhere or type anything on the keyboard except the key you want to assign, in our case it was  cmd+y,  so type  cmd+y Press Enter and you are done. Now you can use  cmd+z  for undo and  cmd+y  to redo like always Intellij It is also as simple as VS-Code Open IntelliJ & go to  Preferences In the search bar search for the  redo. In the ri

My Custom Built Desktop. The Questions & The Answers!

If  you want to avoid overpriced pre-builts like the M1 Mac Mini, Mac Pro, or Dell XPS Desktop without compromising on performance, a self-built desktop is a preferred option. It's also a great choice if you enjoy building things. custom built with ASUS-PRIME-P If you choose to build a custom PC, be prepared to invest time in researching and assembling compatible components.  In this post, I'll share my experience building this colorful powerhouse. I'll cover: Why did I do it.  Key questions to ask when selecting components Thought process behind component choices Components used in my build Benchmark comparisons . ** My second custom-build **.  ***  Disclaimer: Not an Apple product. Just a free apple sticker is used *** Why did I do it I decided to get a desktop during the pre-MacM1 era (yes, that’s a thing). After browsing many websites, I found that well-configured prebuilt PCs were overpriced, while cheaper ones had subpar components. Unable to choose between the option

Time Zones, Meridian, Longitude, IDL… It's more politics than science.

Once, I was working on a few geospatial APIs handling many time zones. While writing tests, I realized I did not know much about timezones. A lame excuse might be, my subpar schooling as a village kid. Nevertheless, I decided to turn the pages on timezones, what I found was more politics than science. Photo by  Arpit Rastogi  on  Unsplash Before diving into anomalies, let’s talk about history then we will go to science followed by politics. History The world without time zones By 300 BCE, the western world agreed that the earth is round. Each developed civilization devised its unique distinct system to measure distances, times & absolute locations, but relative to prime locations within their civilizations. It all worked in ancient times because long-distance travel was not prevalent among common people. Only merchants or armies traveled long distances. And they already developed systems that worked on their predetermined routes, irrespective of the time differences between locatio

BDD (1) — Behavior Driven Development

A wise man ( narcissist me ) once said, “Life is all about the question and answers. The trick to a meaningful life is,  To ask the right questions to yourself, so you can get on the right path to search for the answer .” The very first question one should always ask oneself is WHY.  Let's discuss our WHY in the current case. Why BDD Let's take a step back and start with the well-known software development practice TDD ( Test-Driven Development).  In TDD, the very first thing developers do is, set up the technical expectations from the code by writing failing test cases. After the expectation is set, the code is written/modified to finally pass all of the failing tests. It's an  Acceptance driven development strategy . TDD works fine to create a robust technically working product. But the whole TDD approach revolves only around technical teams. It barely involves the business analysis or product owners to validate the business aspect of a feature, they get involved only aft