1. Introduction
Ah, the bane of every coder’s existence – NullPointerExceptions! Thankfully, in Java 8, we have an elegant solution to this problem: An Optional class. The Optional provides an excellent and neat way to gracefully handle these pesky errors without all that extra boilerplate code. In today’s article, let’s take some time to discover just how powerful and helpful Java Optional class can be for your programming needs!
2. Java Optional Class
The introduction of the Optional class was a significant step forward in Java design, but its introduction has been debatable. Before this, many developers utilized null or exceptions as indications when a specified value wasn’t present; by utilizing the Optional class, we can now explicitly declare if something may be available. Despite these advantages, using it incorrectly can cause more damage than good.
Let’s take a look at different ways how to utilize the Optional objects and their static creational methods:
2.1. Initialize Optional Object Container
Here are three examples of initialising a Java Optional class.
public static void createOptional() { Optional<String> text = Optional.of("Text"); }
The static method of() must receive an element that is a non-null value; otherwise, it will throw NullPointerException.
public static void createOptional() { Optional<String> emptyText = Optional.empty(); }
Creating an empty Optional object with an empty() static method will create an empty Optional instance. However, when an empty Optional is retrieved using get(), it will throw NoSuchElementException.
public static void createOptional() { Optional<String> optionalText = Optional.ofNullable("textTwo"); }
Finally, the ofNullable() method is similar to of() but can handle null elements. When unsure if the contained value is present, we can check using isPresent() before retrieving using the get() method.
3. Difference Between isPresent() and IfPresent()
When a Java Optional object is either returned from a method or created by us, we can determine the present value using the isPresent() method. If the wrapped object contains null values, the ifPresent() method will skip code execution because it’s invoked conditionally. Let’s take a look at two little examples:
if(optionalText.isPresent()) { String element = optionalText.get(); }
The isPresent() returns an Optional describing return boolean value so we can check inside the statement for present value before processing to avoid the NoSuchElementException.
optionalText.ifPresent(element -> { String value = optionalText.get(); System.out.println(value); });
Similarly, the code is only executed when a contained value is present. Yet, this is a functional approach where the consumer interface is used inside a lambda expression.
If you are using at least Java 11, the isEmpty() method is also available, which is the opposite of isPresent(). It will return true on a null value that is not present inside the Java Optional class.
4. IfPresentOrElse()
If we ever have an Optional instance, we would usually like to execute a specific action when value is present. But if the Optional is empty, then different actions should be taken as a substitute; for example, by using a default value. Fortunately, this can all be achieved with the ifPresentOrElse() method – which allows us to pass both a Consumer and Runnable depending on whether or not our value is present.
Here is an example of the ifPresentOrElse() method:
optionalText.ifPresentOrElse(element -> { String value = optionalText.get(); System.out.println(value); }, () -> System.out.println("value is not present"));
5. Optional OrElse() vs OrElseGet()
For programmers who are not yet familiar with Optional class or Java 8, comprehending the distinction between orElse() and orElseGet() can be quite ambiguous. At first glance, they may seem to have similar functionalities; however, if not thoroughly understood, a small but critical difference could significantly affect our code’s performance. Let’s take a closer look at both methods:
public static String optionalOrElse() { String element = null; String retrievedElement = Optional .ofNullable(element) .orElse("test"); return retrievedElement; }
The Optional orElse() provides a default value when the initially specified value is null, resulting in an empty Optional.
public static String optionalOrElseGet() { String element = null; String retrievedElement = Optional .ofNullable(element) .orElseGet(() -> "test"); return retrievedElement; }
Optional orElseGet() looks the same regarding the program’s output, and the logic seems identical; If the output is empty Optional, the second String is returned. However, if we look closely, a slight difference can knock on the performance if not used properly. Let’s take a look at this example:
public static void orElseAndOrElseGet() { String element = "text"; String textOne = Optional.ofNullable(element).orElse(get()); String textTwo = Optional.ofNullable(element).orElseGet(App::get); } private static String get() { System.out.println("Setting optionals"); return "optional text"; }
Optional orElse() will get invoked regardless of whether the element is present, whereas orElseGet() will only be executed when an element is null. This can improve performance significantly if the object is expensive to create, so it’s a good practice to remember.
6. OrElseThrow()
Taking it a step further from the orElse() and orElseGet() methods, instead of returning the default value, the orElseThrow() method throws an exception if the Optional describing value is empty. This approach provides another way of dealing with missing values.
private static String orElseThrow() { String element = null; String retrievedElement = Optional .ofNullable(element) .orElseThrow(NullPointerException::new); return retrievedElement; }
7. Advanced Transformation with Java Optional Class
We’ve already explored several common Optional methods, but there are less common methods to be aware of:
- Transform the Optional type using the map method.
- Transform the Optional type using the flatMap method.
- Apply a condition on the Optional with filter method.
7.1. Transform Using the map Method
Converting the Optionals is pretty simple. Use the map mapping function to transform from one type to another. The map function returns the Optional describing the result of the transformation.
private static void optionalMap() { Optional<Person> emptyPerson = Optional.empty(); Optional<String> name = emptyPerson.map(Person::getName); boolean areBothEmpty = Optional.empty().equals(name); System.out.println(areBothEmpty); }
7.2. Transform Using the flatMap Method
Using the flatMap provided mapping function has almost identical results, but the transformed value needs to be wrapped using Optional, as the flatMap doesn’t do it automatically.
private static void optionalFlatMap() { Optional<Person> emptyPerson = Optional.empty(); Optional<String> name = emptyPerson .flatMap(person -> Optional.of(person.getName())); boolean areBothEmpty = Optional.empty().equals(name); System.out.println(areBothEmpty); }
7.3. Conditional Processing with filter Method
The filter method allows conditional processing that uses predicates to remove records based on a condition. In the example below, we have removed a person if the age is bigger than 20 and, as a result, returned an empty Optional.
private static void optionalFilter() { Optional<Person> person = Optional.of(new Person("Tom", 30)); Predicate<Person> isOver20 = personOne -> personOne.getAge() < 20; Optional<Person> filteredPerson = person.filter(isOver20); System.out.println(filteredPerson.isEmpty()); }
8. When Should you Avoiding Using Java Optional class
Java Optional has proven its worth for returning an empty value instead of null, which is its primary purpose. Although we may be tempted to utilize it in other ways, such actions often have unpleasant repercussions; thus, let’s take a minute to understand when NOT to use Optional.
8.1. As Method Parameters
Nullable arguments cost less memory than Optionals since the compiler has to do the unnecessary wrapping. Also, there is a risk of passing an Optional value parameter that is null, and the method would require some extra logic that is not needed. By taking this action, you are now tasked with managing three different states in your method:
- null
- an empty Optional
- an Optional with value
Compared to a method without Optional, you must handle two different states: a value or null. Therefore consider method overloading when looking for a way to pass nullable arguments.
8.2. As Class Variables
When converting an object containing Optional elements to JSON, the output often incorporates the results of the isPresent() and isEmpty() methods, which may not be the desired result. To ensure your data remains accurate during this processing, take caution when writing Optional values to JSON.
When an object is written to an output stream, a NotSerializableException will be thrown if the class contains a variable of type Optional. This is because Optional cannot be serialized in Java.
9. Summary
Java Optional is a handy tool to prevent your applications from crashing. It allows developers a clever way of dealing with null references, so they don’t have to worry about their program haphazardly throwing you a NullPointerException. Plus, best practice always recommends ditching Optional methods parameters – no exceptions!
Daniel Barczak
Daniel Barczak is a software developer with a solid 9-year track record in the industry. Outside the office, Daniel is passionate about home automation. He dedicates his free time to tinkering with the latest smart home technologies and engaging in DIY projects that enhance and automate the functionality of living spaces, reflecting his enthusiasm and passion for smart home solutions.
Leave a Reply