JavaRush /Java Blog /Random EN /Coffee break #161. How to handle Null in Java using Optio...

Coffee break #161. How to handle Null in Java using Optional

Published in the Random EN group
Source: Medium This article will help you better understand the purpose of Optional when working with Java code. Coffee break #161.  How to handle Null in Java using Optional - 1When I first started working with Java code, I was often advised to use Optional. But at the time I had little understanding of why using Optional was better than implementing handling for null values. In this article, I want to share with you why I think we should all use Optional more, and how to avoid over-optionalizing your code, which is detrimental to code quality.

What is Optional?

The Optional parameter is used to carry objects and enable null references to be handled by various APIs. Let's look at the code snippet:
Coffee coffee = new Coffee();
Integer quantity = coffee.getSugar().getQuantity();
We have a Coffee instance in which we get some sugar from an instance of the Sugar object . If we assume that the quantity value was never set in the Coffee constructor , then coffee.getSugar().getQuantity() will return a NullPointerException . Of course, we can always use good old null checks to fix the problem.
Coffee coffee = new Coffee();
Integer quantity = 0;
if (coffee.getSugar() != null) {
  quantity = coffee.getSugar().getQuantity();
}
Now everything seems to be fine. But when writing Java code, we'd better avoid implementing null checks . Let's see how this can be done using Optional.

How to create Optional

There are three ways to create Optional objects:
  • of(T value) — instantiation of an Optional non-null object. Be aware that using of() to refer to a null object will throw a NullPointerException .

  • ofNullable(T value) - creates an Optional value for an object that can be null.

  • empty() - Creates an Optional instance that represents a reference to null .

// пример использования Optional.of(T Value)
String name = "foo";
Optional<String> stringExample = Optional.of(name)
// пример использования Optional.ofNullable(T Value)
Integer age = null;
Optional<Integer> integerExample= Optional.ofNullable(age)
// пример использования Optional.empty()
Optional<Object> emptyExample = Optional.empty();
So you have an Optional object. Now let's take a look at the two main methods for Optional:
  • isPresent() - This method tells you whether the Optional object contains a non-null value.

  • get() - Retrieves the value for Optional with the current value. Be aware that calling get() on an empty Optional will result in a NullPointerException .

Please note that if you only use get() and isPresent() when working with Optional, you are missing out! To understand this, let's now rewrite the above example with Optional.

Improving Null Checking with Optional

So how can we improve the above code? With Optional we can understand the presence of an object using isPresent() and retrieve it using get() . Let's start by packaging the result of coffee.getSugar() with Optional and using the isPresent() method . This will help us determine if getSugar() returns null.
Coffee coffee = new Coffee();
Optional<String> sugar = Optional.ofNullable(coffee.getSugar());
int quantity = 0;
if (sugar.isPresent()) {
  Sugar sugar = sugar.get();
  int quantity = sugar.getQuantity();
}
Looking at this example, packaging the result of coffee.getSugar() into Optional doesn't seem to add any value, but rather adds hassle. We can improve the result by using what I consider to be my favorite functions from the Optional class:
  • map(Function<? super T,? extends U> mapper) - Maps the value contained in Optional to the provided function. If the Optional parameter is empty, then map() will return Optional.empty() .

  • orElse(T other) is a “special” version of the get() method . It can get the value contained in Optional. However, in the case of an empty Optional, this will return the value passed to the orElse() method .

The method will return the value contained in the Optional instance. But if the Optional parameter is empty, meaning it contains no value, then orElse() will return the value passed to its method signature, which is known as the default value.
Coffee coffee = new Coffee();

Integer quantity = Optional.ofNullable(coffee.getSugar())
    .map(it -> it.getQuantity())
    .orElse(0);
This is really cool - at least I think so. Now, if in case of empty value we do not want to return the default value, then we need to throw some kind of exception. orElseThrow(Supplier<? extends X> exceptionSupplier) returns the value contained in the Optional parameters, or throws an exception if the Optional is empty.
Coffee coffee = new Coffee();

Integer quantity = Optional.ofNullable(coffee.getSugar())
  .map(it -> it.getQuantity())
  .orElseThrow(IllegalArgumentException::new);
As you can see, Optional provides several advantages:
  • abstracts null checks
  • provides an API for handling null objects
  • allows the declarative approach to express what is being achieved

How to become effective with Optional

In my work, I use Optional as a return type when a method can return a “no result” state. I usually use it when defining return types for methods.
Optional<Coffee> findByName(String name) {
   ...
}
Sometimes this is not necessary. For example, if I have a method that returns an int , such as getQuantity() in the Sugar class , then the method might return 0 if the result is null to represent “no quantity”. Now, knowing this, we can think that the Sugar parameter in the Coffee class can be represented as Optional. At first glance, this seems like a good idea since, in theory, sugar doesn't need to be present in coffee. However, this is where I would like to address when not to use Optional. We should avoid using Optional in the following scenarios:
  • As parameter types for POJOs , such as DTOs . Optionals are not serializable, so using them in a POJO makes the object unserializable.

  • As a method argument. If a method argument can be null , then from a pure code perspective, passing null is still preferable to passing Optional. Additionally, you can create overloaded methods to abstractly handle the absence of a null method argument.

  • To represent a Collection object that is missing. Collections can be empty, so an empty Collection , like an empty Set or List , must be used to represent a Collection without values.

Conclusion

Optional has become a powerful addition to the Java library. It provides a way to handle objects that may not exist. Therefore, it should be taken into account when developing methods without falling into the trap of abuse. Yes, you can write great code that implements null checks and null handling, but the Java community prefers to use Optional. It effectively communicates how to handle missing values, is much easier to read than messy Null checks, and results in fewer bugs in your code in the long run.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION