Optional in Java
Ankit Tripathi
Lead Consultant @ ITC Infotech | Master of Science, Full-Stack Development
Every Java programmer must have dealt with NullPointerException. Null pointer exception, which represents absence of a value, can cause many problems in the application. To over come these issues, JAVA 8 introduced a new class java.util.Optional.
Lets try to understand this using a simple example.
String engineModel = car.getEngine().getName();
Although the above code looks okay. But for an electric car the Engine doesn't exist! So, the result of the getEngine() will return null reference. And, the getName() method will be called on a null reference, causing a NullPointerException at runtime.
So, how can we avoid the null pointer exception? Traditionally, we would add checks for null references.
So, our code would look like this :
String engineModel = "";
if(car != null){
CarEngine engine = car.getEngine();
if(engine != null){
engineModel = engine.getName();
}
}
The additional checks makes the code less readable, complex and more error prone. That's why using null references to represent absence of an value is wrong approach and Java 8 introduced Optional class. java.util.Optional<T> class is inspired from Scala and Haskell, i.e. they introduce a new type to encapsulate the null value. This enforces "null checking". Due to this a programmer can no longer "forget" to check the presence or absence of a value.
Java Optional can be viewed as container that can either contain a value or is "empty".
Let's update our Car class to use the Optional.
public class Car {
private Optional<Engine> engine;
private Optional<Motor> motor;
public Optional<Engine> getEngine() { ... }
...
}
public class Engine{
public String getName(){ ... }
}
public class Motor{
public String getName(){ ... }
}
Just a glance at the above code shows that the Car may contain an Engine or a Motor. This code is a clear improvement, as it clearly reflects that engine variable is allowed to be absent. Optional class also provides methods to explicitly deal with both scenarios of presence or absence of the value. The advantage however is that it forces the programmer to think about the cases where the value might not be present.
The intention of introducing the Optional class is not to replace every single null reference. It should be used to design APIs so that, by just reading the method signatures, programmers can tell whether they can expect an optional value. This forces them to actively unwrap an Optional to deal with the absence of the value.
Now, lets see how optionals can be adopted in our code?
1) Creating an empty Optional :
Optional<Engine> ee = Optional.empty();
2) Creating an non-empty Optional :
Engine engine1 = new Engine("petrol", 6);
Optional<Engine> nee = Optional.of(engine1);
In the above code if engine1 was null, NullPointerException would have been thrown immediately.
3) If want to create an Optional which might contain a null value
Optional<Engine> nee2 = Optional.ofNullable(engine1);
4) Use isPresent() method to do a null check :
Optional<Engine> engine1 = ...;
if(engine1.isPresent()) {
engine1.get().startEngine();
}
5) Providing Default values and Actions :
A very common pattern is to return a default value if the optional contains null. We can use orElse() for the same.
Engine engine = engine1.orElse(new Engine());
Similarly, we can throw exceptions if a value is absent, using orElseThrow() method.
领英推荐
Engine engine = engine1.orElseThrow(SomeCustomException::new);
6) To reject/accept a value based on certain condition, we can use filter() Method :
Optional<Engine> engine1 = .....;
engine1.filter(engine -> engine.getName().equals("1965-Model-6-V")).ifPresent(Sysout("its V6 engine"));
// this is equivalent to
if(engine != null && engine.getName().equals("1965-Model-6-V")) {
Sysout("its V6 engine");
}
The filter method takes predicate as an argument. If the optional value is present and satisfies the condition, the filter method returns the value. Otherwise, it returns an Empty Optional object.
7) For extracting information from an object and then transforming value, we can use map Method :
Optional<Engine> engine1 = optionalCarObject.map(car::getEngine);
If the value is present, then the "transformed" by applying the passed function. But nothing happens if Optional is empty. So, it can be called as "checking for null and then extracting". We can even combine map method with the filter method.
optionalCarObject.map(car::getEngine).filer(engine -> engine.getName().equals("1965-Model-6-V")).ifPresent(Sysout("its V6 engine"));
Some of the best practices to follow for Optional class usage :
public Optional<Engine> getEngineByModel() {
// perform a search operation for the required engine model
Optional<Engine> engine = null; // in case no engine
return engine;
}
// this code is not correct. Optional is a container that may or may not hold a value. assigning it null value is useless.
// instead use the below code
public Optional<Engine> getEngineByModel() {
// perform a search operation for the required engine model
Optional<Engine> engine = Optional.empty(); // in case no engine found
return engine;
}
Optional<Engine> engine1 = car.getEngine();
Engine engine = engine1.get();
// Optional can be empty, so calling get() directly throws java.util.NoSuchElementException
// always check first for the presence of a value by using the isPresent() method
if (engine1.isPresent()) {
Engine engine = engine1.get();
....
}
if (engine1.isPresent()) {
Engine engine = engine1.get();
return engine;
}
// instead use orElse()
return engine1.orElse(DEFAULT_ENGINE_MODEL);
// But don't use expensive computations inside orElse() method. Try to use already evaluated values. This will reduce the performance issues.
Optional<String> confName = Optional.of("CodeOne");
if(confName.isPresent())
System.out.println(confName.get().length());
// use this below code instead
confName.ifPresent( s -> System.out.println(s.length()));
Optional<Engine> engine1 = ... ;
if(engine1.isPresent()) {
....
} else {
log.error("Engine not found");
}
// instead use this below code
engine1.ifPresentOrElse(
engine -> .....,
() -> log.error("Engine not found"));
Conclusion
In this article, we saw how to use the java.util.Optional<T> class. Its important to keep in mind, the purpose of Optional is not to replace every single null reference. It helps in designing better Java APIs. Optional class forces developers to actively unwrap an Optional to deal with the absence of a value; resulting in better protection against unintended null pointer exceptions.