Java Optional orElseGet: Lazy Default Values Explained
java optional orelseget: Understand Java Optional orElseGet, its lazy evaluation, and when to use it over orElse to avoid unnecessary computation and side effects.
When you need to supply a default value for an empty Optional, Java gives you two methods: orElse and orElseGet. The difference is subtle but can affect performance and correctness. This article focuses on java optional orelseget, explaining how it works and when it is the right choice.
The Difference Between orElse and orElseGet
orElse(T other) takes a value that is always evaluated, even when the Optional contains a value. orElseGet(Supplier<? extends T> supplier) takes a function that is only invoked when the Optional is empty. This is the core distinction: orElse is eager, orElseGet is lazy.
Consider this example:
Optional<String> maybeName = Optional.of("Alice"); String name = maybeName.orElse(generateDefault()); // generateDefault() runs anyway
Here, generateDefault() is called even though maybeName already contains a value. With orElseGet, the supplier is not called:
String name = maybeName.orElseGet(() -> generateDefault()); // supplier not invoked
The eager behavior of orElse is often harmless when the default is a constant or a simple expression. But if the default requires computation, I/O, or has side effects, the eager evaluation can be wasteful or even incorrect.
How orElseGet Works
The signature of orElseGet is:
public T orElseGet(Supplier<? extends T> supplier)
It accepts a Supplier functional interface, which has a single get() method. The supplier is executed only when the Optional is empty. The return value of the supplier becomes the result of the orElseGet call.
A typical usage with a method reference:
Optional<String> cachedValue = Optional.empty(); String result = cachedValue.orElseGet(this::fetchFromDatabase);
Here, fetchFromDatabase is only called when cachedValue is empty. This is is useful for lazy loading or caching patterns.
When to Use orElseGet
Use orElseGet when the default value is expensive to create, involves I/O, or has side effects that should not occur unless the Optional is actually empty. Common scenarios include:
- Building a default object from a database query.
- Making a network call to fetch a fallback.
- Computing a value that requires significant CPU or memory.
- Invoking a method that logs, or increments a counter.
In these cases, orElse would perform the operation regardless of whether the Optional has a value, leading to unnecessary work and potential bugs. For example, consider a default that increments a counter:
int counter = 0; Optional<String> maybe = Optional.of("present"); String value = maybe.orElse(incrementAndReturnDefault()); // counter increments even though not needed
With orElseGet, the counter is only incremented when the Optional is empty.
Common Mistakes and Pitfalls
One common mistake is assuming orElse also lazily evaluates its argument. It does not. Another pitfall is using orElseGet with a supplier that returns null. While Optional itself can be empty, the supplier's return value can be null, which is allowed but may defeat the purpose of using Optional. If you need to handle null defaults, consider using orElse with a null value or use map and orElse patterns.
Another subtle issue is that orElseGet does not catch exceptions thrown by the supplier. If the supplier throws a checked exception, you must handle it inside the lambda or method reference. This is expected behavior but can surprise developers new to functional interfaces.
Performance and Runtime Behavior
The primary performance benefit of orElseGet is avoiding unnecessary computation. If the default value is a constant or a simple expression, the overhead of the lambda and the lazy evaluation is negligible, and orElse might be clearer. However, when the default is expensive, orElseGet can reduce latency and resource usage.
It is important to note that the lambda itself is allocated once per call, but this is typically negligible compared to the cost of the default computation. The real savings come from not executing the default logic when it is not needed. No benchmark numbers are provided here because the impact depends entirely on the cost of the default supplier.
Alternative Approaches for Default Values
Besides orElseGet, you can use map and orElse to transform the value, or orElseThrow to fail fast. For default values, orElseGet is the idiomatic lazy option. If you need to compute a default based on the absence of a value, orElseGet is the clean choice.
A pattern that combines map and orElseGet is common:
Optional<Integer> length = maybeString.map(String::length); int result = length.orElseGet(() -> computeDefaultLength());
This keeps the transformation lazy and the default lazy as well.
Practical Example: Caching and Expensive Defaults
Consider a service that retrieves a configuration value from a cache, falling back to a remote source. Using orElseGet ensures the remote call is made only when the cache misses:
public String getConfig(String key) { return cache.get(key).orElseGet(() -> fetchFromRemote(key)); }
If cache.get(key) returns an Optional<String> that is present, the remote fetch is never executed. This is a direct use of java optional orelseget to avoid unnecessary network overhead.
When Not to Use orElseGet
If the default is a constant, a literal, or a simple field access, orElse is more readable and has no meaningful downside. For example:
String name = maybeName.orElse("unknown");
Using orElseGet here would add a lambda without benefit. The choice is about clarity and cost. If the the default is a simple expression that is cheap to evaluate, orElse is fine. If the default involves method calls that have side effects or are expensive, orElseGet is the correct tool.