ðĪLazy Results
How to defer expensive calculations with Results
Lazy results optimize performance by deferring costly operations until absolutely necessary. They behave like regular results, but only execute the underlying operation when an actual check for success or failure is performed.
How to Use this Add-On
Add this Maven dependency to your build:
com.leakyabstractions
result-lazy
Maven Central provides snippets for different build tools to declare this dependency.
Creating Lazy Results
We can use LazyResults::ofSupplier
to create a lazy result.
While suppliers can return a fixed success or failure, lazy results shine when they encapsulate time-consuming or resource-intensive operations.
This sample method simply increments and returns a counter for brevity. However, in a typical scenario, this would involve an I/O operation.
Skipping Expensive Calculations
The advantage of lazy results is that they defer invoking the provided Supplier
for as long as possible. Despite this, you can screen and transform them like any other result without losing their laziness.
In this example, the expensive calculation is omitted because the lazy result is never fully evaluated. This test demonstrates that a lazy result can be transformed while maintaining laziness, ensuring that the expensive calculation is deferred.
These methods will preserve laziness:
Triggering Result Evaluation
Finally, when it's time to check whether the operation succeeds or fails, the lazy result will execute it. This is triggered by using any of the terminal methods, such as Result::hasSuccess
.
Here, the expensive calculation is executed because the lazy result is finally evaluated.
Terminal methods will immediately evaluate the lazy result:
Handling Success and Failure Eagerly
By default, Result::ifSuccess
, Result::ifFailure
, and Result::ifSuccessOrElse
are treated as terminal methods. This means they eagerly evaluate the result and then perform an action based on its status.
In this test, we don't explicitly unwrap the value or check the status, but since we want to consume the success value, we need to evaluate the lazy result first.
Furthermore, even if we wanted to handle the failure scenario, we would still need to evaluate the lazy result.
In this other test, we use Result::ifFailure
instead of Result::ifSuccess
. Since the lazy result is evaluated to a success, the failure consumer is never executed.
These methods are treated as terminal when used with regular consumer functions:
Handling Success and Failure Lazily
When these conditional actions may also be skipped along with the expensive calculation, we can encapsulate them into a LazyConsumer
instead of a regular Consumer
. All we need to do is to create the consumer using LazyConsumer::of
. Lazy consumers will preserve the laziness until a terminal method is eventually used on the result.
Here, we use a lazy consumer with Result::ifSuccess
so the expensive calculation is skipped because the lazy result is never fully evaluated.
Conclusion
We learned how to defer expensive calculations until absolutely necessary. By leveraging lazy results, you can optimize performance by avoiding unnecessary computations and only evaluating the operation's outcome when needed.
The full source code for the examples is available on GitHub.
Last updated