Result-Lazy provides lazy versions of Result objects
This library provides lazy versions of Result objects.
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.
Artifact coordinates:
com.leakyabstractions
result-lazy
1.0.0.0
Maven Central Repository provides snippets for different build tools to declare this dependency.
We can use LazyResults::ofSupplier
to create a lazy result.
/* Create new lazy result */
Supplier<Result<Integer, String>> supplier = () -> success(123);
Result<Integer, String> lazy = LazyResults.ofSupplier(supplier); // Just like that!
While suppliers can return a fixed success or failure, lazy results shine when they encapsulate time-consuming or resource-intensive operations.
/* Represents the operation we could omit */
Result<Long, Exception> expensiveCalculation(AtomicLong timesExecuted) {
long counter = timesExecuted.incrementAndGet();
return success(counter);
} // In this example, we just increment and return current count
This sample method simply increments and returns a counter for brevity. However, in a typical scenario, this would involve an I/O operation.
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.
/** The calculation is skipped because the lazy result is not evaluated */
@Test
void shouldSkipExpensiveCalculation() {
AtomicLong timesExecuted = new AtomicLong();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
Result<String, Exception> transformed = lazy.mapSuccess(Object::toString);
// Then
assertNotNull(transformed);
assertEquals(0L, timesExecuted.get());
} // End
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:
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
.
/** The calculation is executed because the lazy result is evaluated */
@Test
void shouldExecuteExpensiveCalculation() {
AtomicLong timesExecuted = new AtomicLong();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
Result<String, Exception> transformed = lazy.mapSuccess(Object::toString);
boolean success = transformed.hasSuccess();
// Then
assertTrue(success);
assertEquals(1L, timesExecuted.get());
} // End
Here, the expensive calculation is executed because the lazy result is finally evaluated.
Terminal methods will immediately evaluate the lazy result:
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.
/** The lazy result is evaluated and then the success consumer is executed */
@Test
void shouldHandleSuccessEagerly() {
AtomicLong timesExecuted = new AtomicLong();
AtomicLong consumerExecuted = new AtomicLong();
Consumer<Long> consumer = x -> consumerExecuted.incrementAndGet();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
lazy.ifSuccess(consumer);
// Then
assertEquals(1L, timesExecuted.get());
assertEquals(1L, consumerExecuted.get());
} // End
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.
/** The lazy result is evaluated but the failure consumer is not executed */
@Test
void shouldHandleFailureEagerly() {
AtomicLong timesExecuted = new AtomicLong();
AtomicLong consumerExecuted = new AtomicLong();
Consumer<Exception> consumer = x -> consumerExecuted.incrementAndGet();
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
lazy.ifFailure(consumer);
// Then
assertEquals(1L, timesExecuted.get());
assertEquals(0L, consumerExecuted.get());
} // End
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:
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.
/** The lazy consumer is not executed because the lazy result is not evaluated */
@Test
void shouldHandleSuccessLazily() {
AtomicLong timesExecuted = new AtomicLong();
AtomicLong consumerExecuted = new AtomicLong();
Consumer<Long> consumer = LazyConsumer
.of(x -> consumerExecuted.incrementAndGet());
// Given
Result<Long, Exception> lazy = LazyResults
.ofSupplier(() -> expensiveCalculation(timesExecuted));
// When
lazy.ifSuccess(consumer);
// Then
assertEquals(0L, timesExecuted.get());
assertEquals(0L, consumerExecuted.get());
} // End
Here, we use a lazy consumer with Result::ifSuccess
so the expensive calculation is skipped
because the lazy result is never fully evaluated.
The full source code for the examples is available on GitHub.
This library adheres to Pragmatic Versioning.
Artifacts are available in Maven Central.
Here you can find the full Javadoc documentation.
We’d love to help. Check out the support guidelines.
If you’d like to contribute to this project, please start here.
This project is governed by the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.
Copyright 2024 Guillermo Calvo.
This library is licensed under the Apache License, Version 2.0 (the “License”); you may not use it except in compliance with the License.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
Permitted:
Required:
Forbidden: