Dernière modification : 12/07/2024

Usage of Optional - Java

 

In this chapter, we will discuss the usage of Optional in Java, which was introduced in version 8.

They were introduced to address the numerous issues related to the null keyword (readability and maintainability problems). They make it easy to check for the presence of an element and to perform operations on it.

1. Creating an Optional

1.1 Empty Optional

To create an empty Optional, use the Optional.empty method.

// Creating an empty Optional
Optional<String> myOptional = Optional.empty();

Note: Never create an Optional with null, as it would defeat its purpose.

1.2 Optional of a non-null element

To create an Optional of a non-null element, use the Optional.of method.

String element = "Alex";
Optional<String> myOptional = Optional.of(element);

Note: If the variable element is null, then an NPE is thrown.

1.3 Optional of a possibly null element

To create an Optional of a possibly null element, use the Optional.ofNullable method.

String element = "Alex";
Optional<String> myOptional = Optional.ofNullable(element);
Optional<String> myOptional2 = Optional.ofNullable(null);

Note: This method is preferable because it is null-safe.

 

2. Checking the presence of an element

After creating an Optional, we can check if it is empty or not (value contained is null) using the isEmpty method (from Java 11) and isPresent.

// non-null element
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.isEmpty()); // false
System.out.println(myOptional.isPresent()); // true

// null element
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.isEmpty()); // true
System.out.println(myOptional2.isPresent()); // false

Note: If no element is provided (Optional.empty), both methods will return the same results as if it was initialized with null.

 

3. Retrieving the element

3.1 Basic retrieval

The quickest (but to be avoided) method is to use Optional.get. This method returns the desired element but throws a NoSuchElementException if it is null. Example:

// non-null element
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.get()); // Alex

// null element
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.get());
// Exception in thread "main" java.util.NoSuchElementException: No value present

3.2 Retrieval with a default value

It is possible to return a default value if the Optional is empty using the Optional.orElse method.

// non-null element
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.orElse("Claire")); // Alex

// null element
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.orElse("Claire")); // Claire

Note: It is possible to put null as the default value.

3.3 Retrieval with a default value from a Supplier

The Optional.orElseGet method allows returning a default value if the Optional is empty. It takes a Supplier (here a lambda) as input.

// non-null element
Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.orElseGet(() -> "Claire")); // Alex

// null element
Optional<String> myOptional2 = Optional.ofNullable(null);
System.out.println(myOptional2.orElseGet(() -> "Claire")); // Claire

Note: This method allows for program optimization. If the method to retrieve a default element is time-consuming, with orElse this time will always be consumed, whether the Optional is empty or not, unlike with orElseGet. The supplier will only be executed if the element is present.

3.4 Throwing an exception if the value is absent

The Optional.orElseThrow method allows throwing an exception if the Optional is empty:

String firstName = Optional.ofNullable(element).orElseThrow(); // NoSuchElementException;

String firstName2 = Optional.ofNullable(element)
                    .orElseThrow(() -> new Exception("The element must not be empty")); // Exception;

 

4. Filters

Just like Streams, Optional allows filtering the content via a predicate. If the content does not match, it will not be retained in subsequent Optional operations.

If the Optional (or the result of the previous operation (filter, map)) is empty, the filter will not be called. Thus, it is not possible to receive null values as input for filtering. Filters are performed using the Optional.filter method:

Optional<Integer> myOptional = Optional.ofNullable(30);
System.out.println(myOptional.filter(age -> age > 20).isPresent()); // true
System.out.println(myOptional.filter(age -> age > 40).filter(age -> age < 100).isPresent()); // false
System.out.println(myOptional.filter(age -> age < 100).isPresent()); // true

Note: Filters can be chained and do not modify the original Optional.

 

5. Mapping

5.1 The map method

Just like Streams, Optional allows mapping the content from one type to another (or transforming the current element) via a Function (here a lambda).

If the Optional (or the result of the previous operation (filter, map)) is empty, the mapping will not be called. Thus, it is not possible to receive null values as input for mapping. Mappings are performed using the Optional.map method. For this example, we have created a Person class containing a firstName field.

class Person {
	private String firstName;

	public Person(String firstName) {
		super();
		this.firstName = firstName;
	}

	public String getFirstName() {
		return firstName;
	}
}

Example usage:

Optional<String> myOptional = Optional.ofNullable("Alex");
System.out.println(myOptional.map(String::toUpperCase).orElse("Claire"));

Person person = new Person("Angel");
String firstName = Optional.ofNullable(person).map(Person::getFirstName).orElse("Claire");

Note: It is possible to combine maps and filters.

 

5.2 Modifying an Optional with flatMap

Unlike traditional mapping, the Optional.flatMap method retrieves the value of the object and allows returning another instance of Optional (empty, different type, etc).

private Optional<String> toLowerCase(String input) {
    return Optional.ofNullable(input.toLowerCase()); // Creating a new Optional
}

Optional<String> myNullable = Optional.ofNullable("Alex");
System.out.println(myNullable.flatMap(this::toLowerCase)); // Optional[alex]

 

6. Conditional Action

It is possible to perform actions only if the Optional is not empty via the Optional.ifPresent method:

// non-empty Optional
Optional<String> myNullable = Optional.ofNullable("Angel");
myNullable.ifPresent(x -> System.out.println(x)); // Angel
myNullable.ifPresent(System.out::println); // Angel

// empty Optional
Optional<String> myNullable2 = Optional.empty();
myNullable2.ifPresent(x -> System.out.println(x)); // lambda not executed

 

Introduced with Java 9, the Optional.ifPresentOrElse method allows defining an action to perform if the Optional is empty and if it is not:

// non-empty Optional
Optional<String> myNullable = Optional.ofNullable("Claire");
myNullable.ifPresentOrElse(System.out::println, () -> System.out.println("Optional is empty"));
// Claire

// Empty Optional
Optional<String> myNullable2 = Optional.empty();
myNullable2.ifPresentOrElse(System.out::println, () -> System.out.println("Optional is empty"));
// Optional is empty

 

7. Alternative Optional

Introduced in Java 9, the Optional.or method allows defining a new Optional if the original one is empty. Otherwise, the original one is kept:

Optional<String> myNullable = Optional.empty();
System.out.println(myNullable.or(() -> Optional.ofNullable("Claire")));
// Optional[Claire]

// With Java 8, the following alternatives are possible:
// Arrays.asList("Claire", "Alexandre"): Alternative to Stream.of
// .stream(): Alternative to Stream.of
Stream.of("Claire", "Alexandre")
        .filter(Objects::nonNull)
        .findFirst();
// Optional[Claire]

// Or with a Supplier
Stream.<Supplier<String>>of(() -> "Claire", () -> "Alexandre")
        .map(Supplier::get)
        .filter(Objects::nonNull)
        .findFirst();
// Optional[Claire]

 

8. Converting a collection to a Stream via an Optional

Several methods allow converting a collection to a Stream via an Optional.

With Java 8:

List<Integer> myList = Arrays.asList(1, 2, 3, 4, null);
Stream<Integer> myStream = Optional.ofNullable(myList)
                           .map(Collection::stream)
                           .orElseGet(Stream::empty);

With Java 9 and the Optional.stream method:

List<Integer> myList = Arrays.asList(1, 2, 3, 4, null);
Stream<Integer> myStream = Optional.ofNullable(myList)
                           .stream()
                           .flatMap(List::stream);

 

9. Conclusion

An Optional can be used multiple times, but care must be taken not to mix up different instances kept. Moreover, an Optional is not serializable, as it does not implement the java.io.Serializable class.

The Optional is an excellent tool, along with Streams, for implementing a functional programming approach and avoiding NPEs.

For further reading, this article provides excellent practices on the subject: "26 Reasons Why Using Optional Correctly Is Not Optional"

LauLem.com - (Pages in French) Conditions of Use - Legal Information - Cookie Policy - Personal Data Protection Policy - About