Exception-safe functional programming constructs providing Result monad for robust error handling and memoization utilities for performance optimization. These utilities enable clean functional composition while maintaining Java's type safety and performance characteristics.
Functional result type representing either a successful value or an error, enabling exception-safe error handling patterns similar to Rust's Result or Haskell's Either.
/**
* Functional result type representing either a value or an error
*/
public interface Result<V> {
/**
* Create result from value or error condition
* @param value Success value (null means error)
* @param error Error message (used if value is null)
* @return Result instance
*/
static <V> Result<V> of(V value, CharSequence error);
/**
* Create successful result
* @param value Success value
* @return Ok result containing value
*/
static <V> Result<V> ok(V value);
/**
* Create error result
* @param error Error message
* @return Error result
*/
static <V> Result<V> err(CharSequence error);
/**
* Create formatted error result
* @param format Error message format string
* @param args Format arguments
* @return Error result
*/
static <V> Result<V> err(String format, Object... args);
/**
* Check if result represents success
* @return true if successful, false if error
*/
boolean isOk();
/**
* Check if result represents error
* @return true if error, false if successful
*/
boolean isErr();
/**
* Get value as Optional
* @return Optional containing value if successful, empty if error
*/
Optional<V> value();
/**
* Get error as Optional
* @return Optional containing error message if error, empty if successful
*/
Optional<String> error();
/**
* Unwrap value or throw ResultException
* @return Success value
* @throws ResultException if result is error
*/
V unwrap();
/**
* Unwrap value or throw ResultException with custom message
* @param message Custom error message
* @return Success value
* @throws ResultException if result is error
*/
V unwrap(CharSequence message);
/**
* Get value or return default
* @param orElse Default value to return if error
* @return Success value or default
*/
V orElse(V orElse);
/**
* Get value or compute from supplier
* @param orElseSupplier Supplier to compute default value
* @return Success value or supplier result
*/
V orElseGet(SupplierWithException<? extends V> orElseSupplier);
/**
* Get value or throw custom exception
* @param throwableSupplier Function to create exception from error message
* @return Success value
* @throws R Custom exception type
*/
<R extends Throwable> V orElseThrow(FunctionWithException<? super String, ? extends R> throwableSupplier) throws R;
/**
* Transform success value using mapper function
* @param mapper Function to transform value
* @return Result with transformed value or original error
*/
<U> Result<U> map(FunctionWithException<? super V, ? extends U> mapper);
/**
* Transform error message using mapper function
* @param mapper Function to transform error message
* @return Result with transformed error or original value
*/
Result<V> mapErr(FunctionWithException<? super String, ? extends CharSequence> mapper);
/**
* Chain results together (monadic bind)
* @param mapper Function returning new Result
* @return Flattened result
*/
<U> Result<U> flatMap(FunctionWithException<? super V, ? extends Result<? extends U>> mapper);
/**
* Recover from error by computing new value
* @param recover Function to compute recovery value from error
* @return Result with recovered value or original success
*/
Result<V> recover(FunctionWithException<? super String, ? extends V> recover);
/**
* Recover from error by computing new Result
* @param recover Function to compute recovery Result from error
* @return Recovered result or original success
*/
Result<V> recoverWith(FunctionWithException<? super String, ? extends Result<? extends V>> recover);
/**
* Process result with separate handlers for success and error
* @param ok Handler for success value
* @param err Handler for error message
*/
void accept(ConsumerWithException<? super V> ok, ConsumerWithException<? super String> err);
/**
* Cast error result to different value type
* @return Error result with different type parameter
*/
<U> Result<U> asError();
}
/**
* Exception thrown when unwrapping failed results
*/
public class ResultException extends RuntimeException {
public ResultException(String message);
public ResultException(Exception cause);
}Usage Examples:
import aQute.bnd.result.Result;
// Basic usage
Result<Integer> divide(int a, int b) {
if (b == 0) {
return Result.err("Division by zero");
}
return Result.ok(a / b);
}
Result<Integer> result = divide(10, 2);
if (result.isOk()) {
System.out.println("Result: " + result.unwrap()); // Result: 5
} else {
System.out.println("Error: " + result.error().get());
}
// Functional composition
Result<String> processNumber(int input) {
return divide(input, 2)
.map(x -> x * 3)
.map(x -> "Processed: " + x)
.recover(err -> "Default processed value");
}
// Chain operations
Result<Integer> chainedOperation = Result.ok(10)
.flatMap(x -> divide(x, 2))
.flatMap(x -> divide(x, 2))
.recover(err -> 0);
// Error handling without exceptions
String result = divide(10, 0)
.map(x -> "Success: " + x)
.orElse("Operation failed");Caching utilities for expensive computations with various eviction strategies and resource management options.
/**
* Memoizing supplier interface that caches computed values
*/
public interface Memoize<S> extends Supplier<S> {
/**
* Create basic memoizing supplier
* @param supplier Supplier to memoize
* @return Memoizing supplier that computes once and caches result
*/
static <T> Memoize<T> supplier(Supplier<? extends T> supplier);
/**
* Create memoizing supplier from function and argument
* @param function Function to memoize
* @param argument Function argument
* @return Memoizing supplier
*/
static <T, R> Memoize<R> supplier(Function<? super T, ? extends R> function, T argument);
/**
* Create time-based refreshing memoizing supplier
* @param supplier Supplier to memoize
* @param time_to_live Time to live for cached value
* @param unit Time unit for TTL
* @return Refreshing memoizing supplier
*/
static <T> Memoize<T> refreshingSupplier(Supplier<? extends T> supplier, long time_to_live, TimeUnit unit);
/**
* Create reference-based memoizing supplier
* @param supplier Supplier to memoize
* @param reference Function to create reference for caching
* @return Reference-based memoizing supplier
*/
static <T> Memoize<T> referenceSupplier(Supplier<? extends T> supplier, Function<? super T, ? extends Reference<? extends T>> reference);
/**
* Create predicate-based memoizing supplier
* @param supplier Supplier to memoize
* @param predicate Predicate to test if cached value is still valid
* @return Predicate-based memoizing supplier
*/
static <T> Memoize<T> predicateSupplier(Supplier<? extends T> supplier, Predicate<? super T> predicate);
/**
* Get memoized value (inherited from Supplier)
* @return Cached or computed value
*/
S get();
/**
* Peek at memoized value without triggering computation
* @return Cached value or null if not computed
*/
S peek();
/**
* Check if value has been computed and cached
* @return true if value is cached, false otherwise
*/
boolean isPresent();
/**
* Transform memoized value using mapper function
* @param mapper Function to transform cached value
* @return New memoized supplier with transformed value
*/
<R> Memoize<R> map(Function<? super S, ? extends R> mapper);
/**
* Chain memoized suppliers together
* @param mapper Function returning new supplier
* @return Flattened memoized supplier
*/
<R> Memoize<R> flatMap(Function<? super S, ? extends Supplier<? extends R>> mapper);
/**
* Filter memoized value with predicate
* @param predicate Filter predicate
* @return Filtered memoized supplier
*/
Memoize<S> filter(Predicate<? super S> predicate);
/**
* Apply consumer to memoized value
* @param consumer Consumer to apply
* @return This memoized supplier
*/
Memoize<S> accept(Consumer<? super S> consumer);
/**
* Apply consumer if value is present
* @param consumer Consumer to apply
* @return This memoized supplier
*/
Memoize<S> ifPresent(Consumer<? super S> consumer);
}
/**
* Memoizing supplier for AutoCloseable resources
*/
public interface CloseableMemoize<S extends AutoCloseable> extends Memoize<S>, AutoCloseable {
/**
* Create closeable memoizing supplier
* @param supplier Supplier producing AutoCloseable resources
* @return Closeable memoizing supplier
*/
static <T extends AutoCloseable> CloseableMemoize<T> closeableSupplier(Supplier<? extends T> supplier);
/**
* Check if this memoizing supplier is closed
* @return true if closed, false otherwise
*/
boolean isClosed();
/**
* Get the memoized AutoCloseable value
* @return The memoized AutoCloseable value
* @throws IllegalStateException if this supplier is closed
*/
@Override
S get();
/**
* Apply consumer to memoized value (thread-safe with closing)
* @param consumer Consumer to apply
* @return This memoized supplier
* @throws IllegalStateException if this supplier is closed
*/
@Override
CloseableMemoize<S> accept(Consumer<? super S> consumer);
/**
* Apply consumer if value is present (thread-safe with closing)
* @param consumer Consumer to apply
* @return This memoized supplier
*/
@Override
CloseableMemoize<S> ifPresent(Consumer<? super S> consumer);
/**
* Close the cached resource if present
* @throws Exception if closing fails
*/
void close() throws Exception;
}Usage Examples:
import aQute.bnd.memoize.Memoize;
import aQute.bnd.memoize.CloseableMemoize;
import java.util.concurrent.TimeUnit;
// Basic memoization
Memoize<String> expensiveResult = Memoize.supplier(() -> {
// Expensive computation
Thread.sleep(1000);
return "Computed result";
});
String result1 = expensiveResult.get(); // Takes 1 second
String result2 = expensiveResult.get(); // Instant (cached)
// Time-based refresh
Memoize<Date> currentTime = Memoize.refreshingSupplier(
() -> new Date(),
5, TimeUnit.MINUTES
);
// Reference-based caching (e.g., weak references)
Memoize<LargeObject> weakCached = Memoize.referenceSupplier(
() -> new LargeObject(),
WeakReference::new
);
// Predicate-based validation
Memoize<Configuration> config = Memoize.predicateSupplier(
() -> loadConfiguration(),
cfg -> cfg.isValid()
);
// Transform cached values
Memoize<Integer> length = expensiveResult.map(String::length);
// Closeable resources
try (CloseableMemoize<Connection> connection = CloseableMemoize.closeableSupplier(
() -> DriverManager.getConnection(url))) {
Connection conn = connection.get();
// Use connection
} // Automatically closes cached connectionExtended functional interfaces that properly handle exceptions in functional pipelines.
/**
* Function that can throw exceptions
*/
@FunctionalInterface
public interface FunctionWithException<T, R> {
R apply(T t) throws Exception;
}
/**
* Consumer that can throw exceptions
*/
@FunctionalInterface
public interface ConsumerWithException<T> {
void accept(T t) throws Exception;
}
/**
* Supplier that can throw exceptions
*/
@FunctionalInterface
public interface SupplierWithException<T> {
T get() throws Exception;
}