avatarUğur Taş

Summary

The provided web content is a comprehensive guide on using the Optional class in Java to handle potentially missing values safely and expressively, thereby reducing the occurrence of NullPointerExceptions.

Abstract

The guide introduces Java 8's Optional class, which encapsulates an optional value and provides methods to handle scenarios where a value might be absent. It emphasizes the importance of Optional in reducing NullPointerExceptions and improving code readability. The article details how to create an Optional using of, ofNullable, and empty methods, access its value with methods like get, orElse, orElseGet, orElseThrow, and ifPresent, and process and convert the value using map, flatMap, filter, and stream. It also contrasts the use of orElse with orElseGet for providing default values and highlights the benefits of using Optional for writing more resilient, readable, and maintainable Java code.

Opinions

  • The author suggests that using Optional is a significant improvement over dealing with null values directly, as it provides a more structured approach to handling the absence of values.
  • The article conveys that while Optional is a powerful tool, it should be used judiciously, particularly avoiding the get method due to its potential to throw an exception if the Optional is empty.
  • The author opines that the orElseGet method is preferable to orElse when providing a default value that requires computation, due to its lazy evaluation.
  • The guide encourages the use of Optional to make code more expressive and to clearly convey the intent behind handling (or not handling) missing values.
  • It is implied that the adoption of Optional can lead to a more enjoyable development process by reducing the occurrence of common bugs related to null values.
  • The author advocates for the exploration of further Java features and best practices, as demonstrated by the links to additional resources and articles on related topics.

Complete Guide for Optional in Java

Optional was introduced in Java 8 as a new core library class to represent the presence or absence of a value. Optional class aimed at reducing the number of NullPointerExceptions in Java applications.

It is a valuable tool for handling scenarios where a value might be missing, without resorting to the infamous NullPointerException (NPE). Optional provides a common interface for representing optional values instead of just using null references. In this comprehensive guide, we will cover when and why to use Optional, how to create and leverage Optionals, and how and when to use methods of Optional.

The Problems Optional Solves: Null Values

Before we dive into Optional, let's understand the problem it addresses. In Java and many other programming languages, null is often used to denote the absence of a value. A major source of bugs in Java programs is the use of null references. Calling methods or accessing fields on a null reference results in a NullPointerException, which can crash your program. To avoid this, Java developers have to litter their code with null checks before using a potentially null object.

if (user != null) {
  System.out.println(user.getName());
}

This results in unreadable and cluttered code. Even with diligent null checking, NPEs may still occur and can be difficult to debug. Also, null comes with significant drawbacks:

  • NullPointerExceptions (NPEs): Accessing a null value results in an NPE, which is a runtime exception that can crash your application if not handled properly.
  • Ambiguity: In some cases, it’s not clear whether a method can return null, leading to confusion and potential bugs.
  • Lack of Expressiveness: Null does not convey why a value is missing. Was it intentional, or is it an error?

Optional was introduced to address these issues by providing a more structured and safer way to represent the absence of a value. Optional forces developers to explicitly think about the possibility of a null value and how to handle it. Hence developer either checks for a value or provides a default.

Creating an Optional

You can create an Optional using three different ways.

1. of

  • Wrap a non-null value using Optional.of(). This is used when you are certain that the value is non-null.
Optional<String> name = Optional.of("John");

2. ofNullable

  • Create an Optional that may contain a null value using Optional.ofNullable()
Optional<String> name = Optional.ofNullable(username);

3. empty

  • Construct an empty Optional with Optional.empty()
Optional<String> empty = Optional.empty();

In most cases, you’ll want to use Optional.ofNullable() to make your intents explicit that the value could be null.

Accessing the Optional Value

1. get

You can use the get() method to access the value inside an Optional. However, be cautious because it can throw a NoSuchElementException if the Optional is empty.

A safer approach is to use isPresent() andget(). Use isPresent() to check if a value is present. It returns true if the optional is not empty. Then retrieve the value with get() call.

if (name.isPresent()) {
  System.out.println(name.get()); 
} else {
  // handle no value
}

However, this method doesn’t contribute significantly to the codebase. Because it does not make any difference then null check (!= null). So it is not recommended.

2. orElse/orElseGet/orElseThrow

If it is possible to provide a default value or a default flow, orElse() and orElseGet() can be used to access the value.

  • orElse()returns the provided default value when the optional is empty.
String blogName = optionalName.orElse("codimis");
  • orElseGet()executes the provided supplier and returns the result value when the optional is empty.
public String getName() {
  return "default";
}

String blogName = optionalName.orElseGet(()-> getName());

There is a crucial difference between orElse() and orElseGet() methods. When you use the orElse() method and provide a default value, the method or expression used to calculate that default value will always be executed, regardless of whether the Optional contains a value or is empty. But the supplier on the orElseGet() method runs only when it is needed. You can see it in the below example.

    public static void main(String[] args) {
        String blogName1 = Optional.of("Codimis").orElseGet(() -> getName());
        System.out.println(blogName1);
        String blogName2 = Optional.of("Example").orElse(getName());
        System.out.println(blogName2);
        
    }
    public static String getName() {
        System.out.println("getName called");
        return "default";
    }

Output of the above code is:

Codimis
getName called
Example

Output contains getName called because orElse() always evaluates the provided default value. Hence, if you don’t have a default value and computation is required to get the default value, using the orElseGet() is suggested because of the lazy evaluation.

  • orElseThrow() is used when you want to throw an exception if the Optional is empty.

This method is particularly useful when you expect an Optional to always contain a value, and if it doesn't, it indicates an exceptional condition that should be handled by throwing an appropriate exception.

import java.util.Optional;
import java.util.NoSuchElementException;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> optionalValue = Optional.empty();
        
        try {
            String result = optionalValue.orElseThrow(() -> new NoSuchElementException("Value is not present"));
            System.out.println("Result: " + result); // This line won't be reached
        } catch (NoSuchElementException ex) {
            System.out.println("Exception: " + ex.getMessage()); // Exception: Value is not present
        }
    }
}

3. ifPresent(Java 8+)/ifPresentOrElse(Java 9+)

Two more ways exist to access and execute a code block with the optional value. They areifPresent() and ifPresentOrElse() methods. Both of them are used to execute a block of code in the event of the existence of the value. They execute the provided consumer block if optional is present. In addition to that, ifPresentOrElse() provides a chance to run a runnable if the value is missing.

name.ifPresent(n -> System.out.println(n));
name.ifPresentOrElse(n->System.out.print(n), ()->System.out.print("no name"));

Processing and Converting the Optional Value

1. map

If you need to apply a function to value of the optional, map() is your function. It applies the given function to the value if present and returns an Optional as a result. If you want to extract a field of an optional value and process it if it exists, you can use map function.

Optional.ofNullable(order)
        .map(Order::getDetails)
        .map(OrderDetails::getEmailAddress)
        .ifPresent(/* send email to user*/);

2. flatMap(Java 9+)

Similar to map but allows the mapping function to return an Optional, then flattens the result. If getDetails method of order instance returns Optional<OrderDetails>, flatMap function avoids having nested Optional object after mapping.

public Optional<OrderDetails> getDetails() {
    return Optional.ofNullable(this.orderDetails);
}

Optional.ofNullable(order)
        .map(Order::getDetails) // returns Optional<Optional<OrderDetails>>
        .map(OrderDetails::getEmailAddress)
        .ifPresent(/* send email to user*/);

Optional.ofNullable(order)
        .flatMap(Order::getDetails) // returns Optional<OrderDetails>
        .map(OrderDetails::getEmailAddress)
        .ifPresent(/* send email to user*/);

3. filter

Returns an Optional containing the value if it satisfies the given predicate, otherwise empty. The returned optional contains initial values. The filter method does not have any impact on the value.

// returns optional empty
Optional.of("Hello, World!").filter(val -> val.contains("Hell,"));
// returns Optional<String>
Optional.of("Codimis").filter(val -> val.contains("Cod"));

4. stream(Java 9+)

Converts the Optional into a Stream containing zero or one element. The stream method is useful when you use optional collections. It can be in two ways, either optional can wrap a list or list can be consist of optional values.

Optional<List<Integer>> optionalList = getListOfIntegers();

// Use stream() to process the list if it's present
optionalList
    .stream()  // Converts the Optional to a Stream
    .flatMap(List::stream)  // Flattens the Stream of lists into a Stream of integers
    .forEach(System.out::println);  // Print each integer
public static void streamExample() {
    List<Optional<BigDecimal>> amounts = new ArrayList<>();
    amounts.add(Optional.of(BigDecimal.TEN));
    amounts.add(Optional.empty());
    amounts.add(Optional.of(BigDecimal.ONE));

    BigDecimal totalAmount = amounts.stream()
        .flatMap(Optional::stream)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

    System.out.println("Total Amount: " + totalAmount);

}

In this comprehensive guide, we’ve covered the fundamentals of Optional, its methods and operations. Armed with this knowledge, you can write Java code that is more resilient, readable, and maintainable. Optional empowers you to handle potentially missing values with grace and clarity, making your codebase more robust and your development process more enjoyable.

👏 Thank You for Reading!

👨‍💼 I appreciate your time and hope you found this story insightful. If you enjoyed it, don’t forget to show your appreciation by clapping 👏 for the hard work!

📰 Keep the Knowledge Flowing by Sharing the Article!

✍ Feel free to share your feedback or opinions about the story. Your input helps me improve and create more valuable content for you.

✌ Stay Connected! 🚀 For more engaging articles, make sure to follow me on social media:

🔍 Explore More! 📖 Dive into a treasure trove of knowledge at Codimis. There’s always more to learn, and we’re here to help you on your journey of discovery.

Java
Optionals
Java Optional
Java8 Optional
Recommended from ReadMedium