Optional in Java

Optional in Java

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 :

  • Never assign null to an variable of Optional type.

 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; 

 }        


  • Never call get() method directly in an Optional.

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();

    ....

}        

  • Instead of using isPresent() and get() methods for setting and returning a value, use orElse()

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.

        

  • Use ifPresent() method, instead of isPresent()-get() methods to perform actions only if Optional is present.

 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()));        

  • Similarly use ifPresentOrElse() for handling cases related to empty-value.

 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"));        

  • And lastly, don't overuse Optionals.


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.


要查看或添加评论,请登录

Ankit Tripathi的更多文章

社区洞察

其他会员也浏览了