Use Optional for Null Safety
In this step, we will explore a more modern approach to handling potential null
values in Java using the Optional
class, introduced in Java 8. Optional
is a container object that may or may not contain a non-null value. It provides a way to represent the presence or absence of a value more explicitly, which can help reduce the risk of NullPointerException
s.
While if (collection != null)
checks are perfectly valid and necessary in many situations, Optional
can make your code more readable and expressive, especially when dealing with methods that might return a value or might return null
.
Let's see how we can use Optional
with a collection. Although Optional
is typically used for single values, you might encounter scenarios where a method returns an Optional<List<SomeObject>>
.
-
Open the HelloJava.java
file in the WebIDE editor.
-
Replace the contents with the following code that demonstrates using Optional
with a potentially null list:
import java.util.List;
import java.util.ArrayList;
import java.util.Optional; // Import Optional
public class HelloJava {
// A method that might return an Optional containing a list, or an empty Optional
public static Optional<List<String>> getNames(boolean includeNames) {
if (includeNames) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
return Optional.of(names); // Return an Optional containing the list
} else {
return Optional.empty(); // Return an empty Optional
}
}
public static void main(String[] args) {
// Case 1: Get names when includeNames is true
Optional<List<String>> namesOptional1 = getNames(true);
System.out.println("Checking namesOptional1:");
// Check if the Optional contains a value
if (namesOptional1.isPresent()) {
List<String> names = namesOptional1.get(); // Get the list from the Optional
System.out.println("List is present. Size: " + names.size());
// You can also check if the list itself is empty
if (names.isEmpty()) {
System.out.println("List is empty.");
} else {
System.out.println("List is not empty. First name: " + names.get(0));
}
} else {
System.out.println("List is not present (Optional is empty).");
}
System.out.println("---");
// Case 2: Get names when includeNames is false
Optional<List<String>> namesOptional2 = getNames(false);
System.out.println("Checking namesOptional2:");
if (namesOptional2.isPresent()) {
List<String> names = namesOptional2.get();
System.out.println("List is present. Size: " + names.size());
if (names.isEmpty()) {
System.out.println("List is empty.");
} else {
System.out.println("List is not empty. First name: " + names.get(0));
}
} else {
System.out.println("List is not present (Optional is empty).");
}
System.out.println("\nProgram finished.");
}
}
In this code:
- We define a method
getNames
that returns an Optional<List<String>>
. This method simulates a scenario where you might get a list, or you might get nothing (represented by an empty Optional
).
- In the
main
method, we call getNames
with true
and false
to test both cases.
- We use
namesOptional.isPresent()
to check if the Optional
contains a list.
- If
isPresent()
is true, we use namesOptional.get()
to retrieve the list. This is safe because we've already checked for presence.
- Inside the
isPresent()
block, we can then perform checks on the list itself, such as names.isEmpty()
.
-
Save the file.
-
Compile the program in the Terminal:
javac HelloJava.java
-
Run the program:
java HelloJava
You should see output similar to this:
Checking namesOptional1:
List is present. Size: 2
List is not empty. First name: Alice
---
Checking namesOptional2:
List is not present (Optional is empty).
Program finished.
This output shows how Optional
helps us handle the case where a list might not be returned at all.
Optional
also provides other useful methods for handling the absence of a value, such as:
orElse(defaultValue)
: Returns the value if present, otherwise returns a default value.
orElseGet(supplier)
: Returns the value if present, otherwise returns the result of the supplier
function.
orElseThrow(exceptionSupplier)
: Returns the value if present, otherwise throws an exception produced by the exceptionSupplier
.
ifPresent(consumer)
: Performs the given action if a value is present.
Let's modify the code to use ifPresent
for a more concise way to handle the list when it's present.
-
Open HelloJava.java
in the editor.
-
Modify the main
method to use ifPresent
:
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
public class HelloJava {
public static Optional<List<String>> getNames(boolean includeNames) {
if (includeNames) {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
return Optional.of(names);
} else {
return Optional.empty();
}
}
public static void main(String[] args) {
// Case 1: Get names when includeNames is true
Optional<List<String>> namesOptional1 = getNames(true);
System.out.println("Checking namesOptional1 using ifPresent:");
namesOptional1.ifPresent(names -> {
// This block only runs if namesOptional1 contains a list
System.out.println("List is present. Size: " + names.size());
if (names.isEmpty()) {
System.out.println("List is empty.");
} else {
System.out.println("List is not empty. First name: " + names.get(0));
}
});
if (!namesOptional1.isPresent()) { // Still need a check if you need to handle the absence case
System.out.println("List is not present (Optional is empty).");
}
System.out.println("---");
// Case 2: Get names when includeNames is false
Optional<List<String>> namesOptional2 = getNames(false);
System.out.println("Checking namesOptional2 using ifPresent:");
namesOptional2.ifPresent(names -> {
System.out.println("List is present. Size: " + names.size());
if (names.isEmpty()) {
System.out.println("List is empty.");
} else {
System.out.println("List is not empty. First name: " + names.get(0));
}
});
if (!namesOptional2.isPresent()) {
System.out.println("List is not present (Optional is empty).");
}
System.out.println("\nProgram finished.");
}
}
We replaced the if (namesOptional.isPresent()) { ... namesOptional.get() ... }
structure with namesOptional.ifPresent(names -> { ... })
. The code inside the lambda expression (names -> { ... }
) will only execute if the Optional
contains a value. We still added an if (!namesOptional.isPresent())
check to handle the case where the Optional is empty, as ifPresent
only handles the presence case.
-
Save the file.
-
Compile the program:
javac HelloJava.java
-
Run the program:
java HelloJava
The output should be the same as before, demonstrating that ifPresent
provides an alternative way to handle the presence of a value in an Optional
.
Using Optional
can make your code's intent clearer regarding whether a value might be absent, and it encourages you to handle that absence explicitly, reducing the likelihood of unexpected NullPointerException
s.