Java 8 introduced the Optional class to handle nullable values more effectively and concisely. It eliminates the need for explicit null checks and provides a safer and cleaner way to deal with optional values.
Key Features of Optional
There are several ways of creating Optional objects.
a. Optional.empty(): To create an empty Optional object, we simply need to use its empty() static method.
Optional emptyOptional = Optional.empty();
b. Optional.of(T value): Creates an Optional containing the specified non-null value.
Optional optionalValue = Optional.of("Hello, World!");
c. Optional.ofNullable(T value): Creates an Optional containing the specified value, or an empty Optional if the value is null.
String nullableValue = getNullableValue();
Optional optionalNullable = Optional.ofNullable(nullableValue);
a. T get(): Gets the value if present, otherwise throws NoSuchElementException.
Optional optionalValue = Optional.of("Hello, World!");
String value = optionalValue.get();
System.out.println(value); // Output: Hello, World!
b. T orElse(T other): Returns the value if present, otherwise returns the specified default value.
Optional optionalValue = Optional.empty();
String result = optionalValue.orElse("Default Value");
System.out.println(result); // Output: Default Value
c. T orElseGet(Supplier<? extends T> other): Returns the value if present, otherwise invokes the supplier and returns the result of that invocation.
Optional optionalValue = Optional.empty();
String result = optionalValue.orElseGet(() -> "Default Value");
System.out.println(result); // Output: Default Value
d. T orElseThrow(Supplier<? extends X> exceptionSupplier): Returns the value if present, otherwise throws an exception produced by the provided supplier.
Optional optionalValue = Optional.empty();
String result = optionalValue.orElseThrow(() -> new NoSuchElementException("No value present"));
orElse(): The orElse() method takes a parameter as the default value to return if the Optional object is empty. It always evaluates the expression passed to it, regardless of whether the Optional object contains a value or not. This can lead to unnecessary computations if the Optional object is not empty. Here’s an example:
import java.util.Optional;
public class OrElseExample {
public static void main(String[] args) {
Optional optionalValue = Optional.ofNullable(getValueFromExternalSource());
String result = optionalValue.orElse("Default Value");
System.out.println("Result: " + result);
}
private static String getValueFromExternalSource() {
// Simulating a method that may return null
return null;
}
}
In this example, if getValueFromExternalSource returns null, the orElse method will provide the default value “Default Value.”
orElseGet Method: The orElseGet method is similar to orElse but with a key difference. Instead of taking a direct default value, it takes a Supplier as a parameter. A Supplier is a functional interface representing a supplier of results. The Supplier is lazily evaluated, meaning it is only invoked when the optional value is not present. This can be more efficient if creating the default value is expensive or has side effects. Here’s an example:
import java.util.Optional;
public class OrElseGetExample {
public static void main(String[] args) {
Optional optionalValue = Optional.ofNullable(getValueFromExternalSource());
String result = optionalValue.orElseGet(() -> generateDefaultValue());
System.out.println("Result: " + result);
}
private static String getValueFromExternalSource() {
// Simulating a method that may return null
return null;
}
private static String generateDefaultValue() {
System.out.println("Generating default value");
return "Default Value";
}
}
In this example, if getValueFromExternalSource returns null, the orElseGet method will invoke the generateDefaultValue method only when needed.
Performance Implications: The orElseGet() method is generally more performant than the orElse() method, as it avoids unnecessary computations if the Optional object is not empty. This is especially important if the default value provider is an expensive operation.
When to Use Each Method: Use orElse() when you already have a default value readily available and don’t need to perform any additional computations to generate the default value. Use orElseGet() when you need to perform computations to generate the default value, especially if those computations are expensive.
When we have an Optional object returned from a method or created by us, we can check if there is a value in it or not with the isPresent() method:
Optional optionalValue = Optional.of("Hello, World!");
if (optionalValue.isPresent()) {
System.out.println("Value is present!");
}
b. void ifPresent(Consumer<? super T> consumer): If a value is present, perform the given action with the value, otherwise does nothing.
Optional optionalValue = Optional.of("Hello, World!");
optionalValue.ifPresent(val -> System.out.println("Value: " + val));
c. public boolean isEmpty(): Also, as of Java 11, we can do the opposite with the isEmpty method. The isEmpty() method is an alternative to the isPresent() method, which also checks whether or not an Optional object contains a value. However, the isEmpty() method is more concise and easier to read.
Optional name = Optional.empty();
if (name.isEmpty()) {
System.out.println("Name is empty");
} else {
System.out.println("Name is present: " + name.get());
}
a.Transforming Value With map(): If a value is present, apply the provided mapping function, otherwise returns an empty Optional.
Syntax: Optional<U> map(Function<? super T, ? extends U> mapper)
Optional optionalValue = Optional.of("Hello, World!");
Optional length = optionalValue.map(String::length); //Optional[13]
b. Transforming Value With flatMap(): If a value is present, apply the provided Optional-bearing mapping function, otherwise returns an empty Optional.
Syntax: Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
Optional optionalValue = Optional.of("Hello, World!");
Optional result = optionalValue.flatMap(val -> Optional.of(val.toUpperCase())); //Optional[HELLO, WORLD!]
a. Optional<T> filter(Predicate<? super T> predicate): If a value is present and the value matches the given predicate, returns an Optional describing the value, otherwise returns an empty Optional.
Optional optionalValue = Optional.of("Hello, World!");
Optional filteredValue = optionalValue.filter(val -> val.startsWith("Hello")); //Optional[Hello, World!]
The Optional class is a powerful tool for handling nullable values in Java. It provides a concise and expressive way to deal with optional values, making code more readable, maintainable, and less prone to NullPointerExceptions. By embracing the use of Optional, you can write more robust and efficient Java code.