Class Lazy<T>

java.lang.Object
dmx.fun.Lazy<T>
Type Parameters:
T - the type of the lazily evaluated value

@NullMarked public final class Lazy<T> extends Object
A lazily evaluated value that is computed at most once.

The supplier passed to of(Supplier) is not invoked until the first call to get(). Subsequent calls return the cached result without invoking the supplier again. Memoization is thread-safe via a volatile Try<T> state field and double-checked locking: the first read is lock-free; if the state is null, a synchronized block is entered and the state is checked again before evaluating the supplier. Both successful values and supplier exceptions are stored in the state as a Try, so any exception thrown during evaluation is memoized and rethrown on every subsequent get() call without re-invoking the supplier. The thread-safety design is documented in ADR-012 — Lazy<T> with volatile Try<T> and double-checked locking.

This type is @NullMarked: the supplier must return a non-null value. Use Lazy<Option<T>> to model a lazily evaluated optional result.

  • Method Details

    • of

      public static <T> Lazy<T> of(Supplier<? extends T> supplier)
      Creates a new Lazy that will evaluate supplier on the first call to get().
      Type Parameters:
      T - the type of the value
      Parameters:
      supplier - the supplier to evaluate lazily; must not be null
      Returns:
      a new, unevaluated Lazy<T>
      Throws:
      NullPointerException - if supplier is null
    • get

      public T get()
      Returns the value, evaluating the supplier on the first call and caching the result.

      The supplier is called at most once. All subsequent calls return the cached value. This method is thread-safe.

      Returns:
      the (possibly cached) value; never null
      Throws:
      NullPointerException - if the supplier returns null
    • isEvaluated

      public boolean isEvaluated()
      Returns true if the supplier has already been evaluated.
      Returns:
      true after the first call to get(), false before
    • map

      public <R> Lazy<R> map(Function<? super T, ? extends R> f)
      Returns a new Lazy that applies f to this value when evaluated.

      The mapping function is not invoked until get() is called on the returned Lazy. Evaluating the returned Lazy also evaluates this one.

      Type Parameters:
      R - the type of the mapped value
      Parameters:
      f - the mapping function; must not be null and must not return null
      Returns:
      a new, unevaluated Lazy<R>
      Throws:
      NullPointerException - if f is null
    • flatMap

      public <R> Lazy<R> flatMap(Function<? super T, Lazy<? extends R>> f)
      Returns a new Lazy by applying f to this value and flattening the result.

      Neither this value nor the function is evaluated until get() is called on the returned Lazy.

      Type Parameters:
      R - the type of the resulting value
      Parameters:
      f - a function that returns a Lazy<R>; must not be null
      Returns:
      a new, unevaluated Lazy<R>
      Throws:
      NullPointerException - if f is null or returns null
    • toOption

      public Option<T> toOption()
      Evaluates this Lazy and wraps the result in an Option.

      Unlike toTry(), this method does not capture exceptions — any unchecked throwable (RuntimeException or Error) thrown by the supplier propagates to the caller, with the same semantics as get(). Use toTry().toOption() if you need exception-safe conversion.

      Returns:
      Option.some(value)
      Throws:
      RuntimeException - if the supplier throws a RuntimeException
      Error - if the supplier throws an Error
    • toTry

      public Try<T> toTry()
      Evaluates this Lazy inside a Try, capturing any exception thrown by the supplier as a Failure.

      The result is memoized: the supplier is called at most once regardless of how many times this method is invoked.

      Returns:
      Success(value) if the supplier completes normally, or Failure(exception) if it throws
    • fromFuture

      public static <T> Lazy<T> fromFuture(CompletableFuture<? extends T> future)
      Creates a Lazy that defers the blocking wait for future until the first call to get().

      The future's result is obtained via Try.fromFuture(CompletableFuture), which unwraps CompletionException transparently. If the future completed exceptionally, get() rethrows the original RuntimeException or Error as-is, and wraps checked exceptions in a new RuntimeException.

      Type Parameters:
      T - the type of the future's value
      Parameters:
      future - the CompletableFuture to wrap; must not be null
      Returns:
      a new, unevaluated Lazy<T>
      Throws:
      NullPointerException - if future is null
    • toFuture

      public CompletableFuture<T> toFuture()
      Converts this Lazy into a CompletableFuture.

      If the value has already been evaluated (i.e., isEvaluated() is true), returns an already-completed future with the cached value — no thread pool dispatch occurs. Otherwise, evaluates the supplier asynchronously via CompletableFuture.supplyAsync(Supplier).

      Returns:
      a CompletableFuture<T> that completes with this lazy's value
    • toResult

      public Result<T, Throwable> toResult()
      Evaluates this Lazy inside a Result, capturing any exception thrown by the supplier as Err(Throwable).
      Returns:
      Ok(value) if the supplier completes normally, or Err(exception) if it throws
    • toResult

      public <E> Result<T,E> toResult(Function<? super Throwable, ? extends E> errorMapper)
      Evaluates this Lazy inside a Result, converting any exception thrown by the supplier into a typed error using errorMapper.
      Type Parameters:
      E - the error type
      Parameters:
      errorMapper - a function that converts the thrown exception to the error value; must not be null and must not return null
      Returns:
      Ok(value) if the supplier completes normally, or Err(errorMapper.apply(exception)) if it throws
      Throws:
      NullPointerException - if errorMapper is null or returns null
    • toOptional

      public Optional<T> toOptional()
      Evaluates this Lazy and returns the value wrapped in a Optional.

      Unlike toTry(), this method does not capture exceptions — any unchecked throwable (RuntimeException or Error) thrown by the supplier propagates to the caller, with the same semantics as get(). Use toTry().toOptional() if you need exception-safe conversion.

      Returns:
      Optional.of(value); never Optional.empty()
      Throws:
      RuntimeException - if the supplier throws a RuntimeException
      Error - if the supplier throws an Error
    • toEither

      public Either<Throwable, T> toEither()
      Evaluates this Lazy inside an Either, capturing any exception thrown by the supplier as Left(Throwable).
      Returns:
      Either.right(value) if the supplier completes normally, or Either.left(exception) if it throws
    • toEither

      public <E> Either<E,T> toEither(Function<? super Throwable, ? extends E> errorMapper)
      Evaluates this Lazy inside an Either, converting any exception thrown by the supplier into a typed left value using errorMapper.
      Type Parameters:
      E - the left (error) type
      Parameters:
      errorMapper - a function that converts the thrown exception to the left value; must not be null and must not return null
      Returns:
      Either.right(value) if the supplier completes normally, or Either.left(errorMapper.apply(exception)) if it throws
      Throws:
      NullPointerException - if errorMapper is null or returns null
    • toString

      public String toString()
      Returns a string representation of this Lazy.
      Overrides:
      toString in class Object
      Returns:
      "Lazy[?]" if not yet evaluated, "Lazy[value]" if evaluated successfully, or "Lazy[!]" if evaluation failed