avatarUğur Taş

Summary

The provided web content discusses best practices and common pitfalls when using the Optional class in Java, introduced in Java 8 to handle potentially null values safely.

Abstract

The article "Optional Best Practices" delves into the appropriate use of Java's Optional class, emphasizing its role in mitigating the risk of null pointer exceptions. It outlines recommended practices such as using Optional.empty() for methods that might not return a value, employing ifPresent() for safe value extraction, and utilizing orElseGet() for lazy default values. The article also advises on using orElseThrow() for custom exceptions and integrating Optional with the Stream API. On the flip side, it warns against bad practices like using isPresent() as a null check replacement, employing Optional as method parameters, using Optional.get() directly, having Optional object fields, nesting Optional instances, and overusing Optional. The author stresses the importance of understanding where and how to use Optional effectively to enhance code safety and readability.

Opinions

  • The author believes that using Optional is a game-changer for handling potentially null values in Java, indicating a strong preference for its use over traditional null checks.
  • There is an emphasis on the idea that Optional should be primarily used as a return type rather than as a method parameter or object field due to potential overhead and serialization issues.
  • The article suggests that proper use of Optional can lead to cleaner, safer, and more expressive code, while improper use can lead to less readable and maintainable code.
  • The author advocates for lazy evaluation of default values using orElseGet() to avoid unnecessary computations with orElse().
  • The preference for method overloading instead of using Optional as a parameter type is clearly stated, suggesting a belief that this approach provides clearer method signatures.
  • The author expresses that Optional should not be overused and should be reserved for situations where a value might be absent, not for non-nullable values.
  • The opinion that nested Optional instances should be avoided in favor of flattening with flatMap() is presented to improve code readability and understandability.
  • Encouragement for developers to handle absent values explicitly is conveyed, with the author asserting that this is the primary purpose of using Optional.
  • The article concludes with a recommendation for ZAI.chat, an AI service alternative to ChatGPT Plus, indicating the author's endorsement of the service for its cost-effectiveness and performance.

Optional Best Practices

Real-world Use Cases

Java introduced a game-changer with the advent of Java 8, the Optional class. Optional was designed to address a long-standing problem: handling of potentially null values. In this article, we will explore the best practices and bad practices of Java Optional to help you to understand when and how to use Optional

If you don’t have a medium membership, you can use this link to reach the article without a paywall.

Best Practices

Use Optional.empty()

If your business requirements force you to return null, use optional as a return type. When a method might not have a meaningful result, consider returning an Optional . Let’s explore it with an example. If you want to retrieve your user data with the id field, there is a chance that the id value does not belong to any of the stored users. Hence, you can return a null user object. In that case, Optional is the best candidate as a return type.

public User findUserById(int id) {
    // this method can return null
}
public Optional<User> findUserById(int id) {
    // this method can return Optional.empty 
    // But it forces the developer to make necessary checks
}

Prefer ifPresent() for Value Extraction

Instead of using get() to extract a value, use ifPresent(Consumer<? super T> consumer) to perform an action if the Optional contains a value. This avoids the risk of NoSuchElementException. This does not provide any performance advantage but reduces the verbosity of the code.

if (user.isPresent()) {
    System.out.println("user: " + user);
}
user.ifPresent(System.out::println);

Consider orElseGet() for Lazy Default Values

If creating the default value is expensive or depends on conditions, use orElseGet(Supplier<? extends T> supplier) to provide a lazy default value. If you acquire the default value with computation, using orElse() is not a proper option. Because it always evaluates the provided method to extract the default value regardless of the existence of value in optional.

Use orElseThrow() for Custom Exceptions

When the absence of a value is exceptional, use orElseThrow(Supplier<? extends X> exceptionSupplier) to throw a custom exception with a descriptive message.

public void updateOrder(long userId, Order order) {
    User user = userService.getUserById(userId).orElseThrow(ResourceNotFoundException::new) ;
    order.addUser(user);
}

Use with Stream API

Optional integrates well with the Stream API. When working with streams, you can use methods like filter(), map(), and flatMap() to work with optional values in a functional style.

Optional.of(order)
        .filter(o -> o.user != null)
        .map(Order::getUser)
        .map(User::getAge);

By using functional methods, NullPointerException is easily avoided.

Bad Practices

Using isPresent() as a replacement for the null check

Using isPresent() function as a replacement for the null check is not a good practice. Instead of using it that way, it is better to use it as an indicator of the existence of the value to decide the next step.

boolean keepVotingOpen = users.stream()
                              .filter(User::notVoted)
                              .findFirst()
                              .isPresent();

When you use the isPresent function for other purposes, you can consider it a bad practice.

Using Optional as Parameters

Using Optional for method parameters can introduce unnecessary overhead. Optional is designed to be used for return values, not method parameters.

When you use Optional as a method parameter, it can make the method signature less clear. It's not immediately obvious whether the method expects the parameter to be present or not, leading to confusion for developers who use your code.

Also if a parameter is expected to be null, method overloading is better approach then using optional as parameter.

// Bad practice
public List<User> getUser(Optional<Boolean> voted) {
    // DO something
}
// Good practice
public List<User> getUser() {
    return getUser(null);
}
public List<User> getUser(Boolean voted) {
    // DO something
}

Using Optional.get directly

The primary purpose of using Optional is to clearly indicate that a value might be absent and encourage developers to handle that case explicitly. Using get() bypasses this safety mechanism, defeating the purpose of using Optional in the first place.

Using as an Object Field

Fields are often used to represent the state of an object. If you use an Optional field, it may expose the internal state of the object in a way that doesn't respect encapsulation principles. Additionally, it can make it challenging to ensure the immutability of the object, as Optional itself is mutable (you can change its value).

Moreover, when serializing objects, especially when dealing with older serialization frameworks, Optional fields can introduce compatibility issues. Some frameworks may not handle them correctly, leading to unexpected behavior when deserializing objects.

public class User {
    private String name;
    private Optional<Boolean> voted;
}

Nesting Optionals

Avoid nesting Optional instances within each other. It can make code less readable and harder to understand. Instead, consider using methods like flatMap() to flatten nested Optional structures.

// nested optional
Optional<Optional<Integer>> mapped = optional.map(val -> Optional.of(val.length()));
// optional flattened with flatMap
Optional<Integer> flatMapped = optional.flatMap(val -> Optional.of(val.length()));

Overuse of Optional

Use Optional sparingly where appropriate. Don’t reflexively use it everywhere. It’s meant for situations where a value might be absent. For non-nullable values, prefer using the actual type without Optional. Consider this example. Application needs to show state of an order and there is a method to extract the state of the order. Using Optional as a return type for this method is not meaningful.

public Optional<String> fetchStateBadExample(Order order) {
    User user = order.getUser();
    return user != null ? 
           Optional.ofNullable(order.getState()) : 
           Optional.of("NOT_STARTED");
}
public String fetchStateGoodExample(Order order) {
    User user = order.getUser();
    return user != null && order.getState() !=null ? 
           order.getState() : 
           "NOT_STARTED";
}

Java Optional is a powerful tool that enables cleaner, safer, and more expressive code by addressing the problem of null values. By following best practices and understanding its capabilities, you can confidently use Optional to improve your Java codebase and reduce the risk of null pointer exceptions.

👏 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 and subscribe

📰 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
Optional Type
Optionals
Best Practices
Optional Chaining
Recommended from ReadMedium