Interface LazyConstant<T>

Type Parameters:
T - type of the constant
All Superinterfaces:
Supplier<T>

public sealed interface LazyConstant<T> extends Supplier<T>
LazyConstant is a preview API of the Java platform.
Programs can only use LazyConstant when preview features are enabled.
Preview features may be removed in a future release, or upgraded to permanent features of the Java platform.
A lazy constant is a deferred, shallowly immutable constant to be computed at a later time via an underlying computing function.

A lazy constant is created using the factory method of(Supplier). When created, the lazy constant is not initialized, which means the constant is not yet set. The constant, of type T, can then be initialized (and retrieved) by calling get(). The first time get() is called, an underlying computing function will be invoked which would compute the constant. The computing function is provided at construction. Once initialized, the constant can never change and can be retrieved over and over again by subsequent get invocations.

The term "shallowly immutable" means the reference to the constant will never change once it is initialized. However, the referenced object itself may or may not be mutable. Hence, immutability can only be guaranteed at the first initial, shallow level.

Consider the following example where a lazy constant field "logger" is a shallowly immutable holder of a constant of type Logger:

 public class Component {

    // Creates a new uninitialized lazy constant
    private final LazyConstant<Logger> logger =
            LazyConstant.of( () -> Logger.create(Component.class) );

    public void process() {
        logger.get().info("Process started");
        // ...
    }
 }

Initially, the lazy constant is not initialized, until logger.get() evaluates the computing function, and initializes the constant to the result; the result is then returned to the client. Hence, get() guarantees that a lazy constant is initialized before it returns, barring any exceptions.

Furthermore, get() guarantees that, out of several threads trying to invoke the computing function simultaneously, only one is ever selected for computation. This property is crucial as evaluation of the computing function may have side effects, for example, the call above to Logger.create() may result in storage resources being prepared.

Exception handling

If the computing function returns null, a NullPointerException is thrown. Hence, a lazy constant can never be null. Clients that want to use a nullable constant can wrap the value into an Optional holder.

If the computing function recursively invokes itself (directly or indirectly via the lazy constant), an IllegalStateException is thrown and the lazy constant is not initialized.

If an invocation of the computing function is unsuccessful (i.e., the function throws), the throwable is relayed to the caller and the lazy constant is not initialized. If the computing function throws any unchecked exception or Error, that Throwable is propagated to the caller, and the lazy constant remains uninitialized. In other words, upon an unsuccessful invocation of the computing function, neither a constant, the exception, nor the fact that an exception was thrown are ever stored in the lazy constant.

Composing lazy constants

A lazy constant can depend on other computed constants, forming a dependency graph that can be lazily computed but where access to individual elements can still be performant. In the following example, a single Foo and a Bar instance (that is dependent on the Foo instance) are lazily created, both of which are held by lazy constants:
 public final class DependencyUtil {

     private DependencyUtil() {}

     public static class Foo {
          // ...
      }

     public static class Bar {
         public Bar(Foo foo) {
              // ...
         }
     }

     private static final LazyConstant<Foo> FOO = LazyConstant.of( Foo::new );
     private static final LazyConstant<Bar> BAR = LazyConstant.of( () -> new Bar(FOO.get()) );

     public static Foo foo() {
         return FOO.get();
     }

     public static Bar bar() {
         return BAR.get();
     }

 }
Calling BAR.get() will create the Bar singleton if it is not already created. Upon such a creation, a dependent Foo will first be created if the Foo does not already exist.

Thread Safety

A lazy constant is guaranteed to be initialized atomically and at most once. If competing threads are racing to initialize a lazy constant, only one updating thread runs the computing function, while the other threads are blocked until the constant is initialized, after which the other threads observe the lazy constant is initialized and leave the constant unchanged and will never invoke any computation.

The invocation of the computing function and the resulting initialization of the constant happens-before the initialized constant is read. Hence, the initialized constant, including any final fields of any newly created objects, are safely published.

The computing function runs on the caller’s thread.

If a thread that is blocked by another computing thread is interrupted, this is not acted upon by the lazy constant (e.g., the thread’s interrupted status is not cleared, and it does not throw InterruptedException; interruption does not cancel initialization).

If the computing function blocks indefinitely, other threads operating on this lazy constant may block indefinitely; no timeouts or cancellations are provided.

Performance

As a lazy constant can never change after it has been initialized. Therefore, a JVM implementation may, for an initialized lazy constant, elide all future reads of that lazy constant, and instead directly use any constant that it has previously observed. This is true if the reference to the lazy constant is a VM constant (e.g. in cases where the lazy constant itself is stored in a static final field) or forms a trusted chain to such a VM constant via one or more layers of a record fields or final fields in hidden classes.
API Note:
As a lazy constant can be initialized with an object but, it is not possible to ever remove that object, this can be a source of an unintended memory leak. In other words, a lazy constant strongly references the object it was initialized with. Hence, a lazy constant will hold the object it was initialized with until the lazy constant itself is collected (if ever).

A LazyConstant that has a type parameter T that is an array type (of arbitrary rank) will only allow the JVM to treat the array reference as a constant but not its components. Instead, a a computed listPREVIEW of arbitrary depth can be used, which provides constant components. More generally, a lazy constant can hold other lazy constants of arbitrary depth and still provide transitive constancy.

The LazyConstant type is not Serializable.

It is not recommended putting lazy constants into equality-based collections (or similar constructs) prior to initialization if the underlying functions have side effects or may fail.

Use in static initializers may interact with class initialization order; cyclic initialization may result in initialization errors per JLS 12.4.

Implementation Requirements:
Except for equals(obj) and orElse(Object) parameters; all method parameters must be non-null or a NullPointerException will be thrown.
Implementation Note:
A lazy constant is free to synchronize on itself. Hence, care must be taken when directly or indirectly synchronizing on a lazy constant.
See Java Language Specification:
12.2 Initialization of Classes and Interfaces
17.4.5 Happens-before Order
Since:
26
See Also:
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    Indicates whether some other object is "equal to" this lazy constant.
    get()
    Returns the initialized constant. If not initialized, first computes and initializes the constant using the computing function.
    int
    Returns the hash code of this constant.
    boolean
    Returns true if the constant is initialized, false otherwise.
    static <T> LazyConstantPREVIEW<T>
    of(Supplier<? extends T> computingFunction)
    Returns a new lazy constant to be computed later via the provided computingFunction.
    orElse(T other)
    Returns the constant if initialized, otherwise, returns other.
    Returns a non-initializing string suitable for debugging.
  • Method Details

    • orElse

      T orElse(T other)
      Returns the constant if initialized, otherwise, returns other.

      This method never triggers initialization of this lazy constant and will observe initialization by other threads atomically (i.e., it returns the initialized value if and only if the initialization has already completed).

      Parameters:
      other - value to return if the constant is not initialized (can be null)
      Returns:
      the constant if initialized, otherwise, returns other
    • get

      T get()
      Returns the initialized constant. If not initialized, first computes and initializes the constant using the computing function.

      After this method returns successfully, the constant is guaranteed to be initialized.

      If the computing function throws, the throwable is relayed to the caller and the lazy constant remains uninitialized; a subsequent call to get() may then attempt the computation again.

      Specified by:
      get in interface Supplier<T>
      Returns:
      the initialized constant. If not initialized, first computes and initializes the constant using the computing function
    • isInitialized

      boolean isInitialized()
      Returns true if the constant is initialized, false otherwise.

      This method never triggers initialization of this lazy constant and will observe changes in the initialization state made by other threads atomically.

      Returns:
      true if the constant is initialized, false otherwise
    • equals

      boolean equals(Object obj)
      Indicates whether some other object is "equal to" this lazy constant.

      The other object is considered equal if:

      • it is also an instance of LazyConstant and;
      • the constant values obtained via get() are "equal to" each other via equals().

      In other words, equality is based solely on the initialized constants, not on computing functions or lazy constants' identities.

      This method may trigger initialization of this lazy constant and/or the provided obj, if it is an instance of a LazyConstant. Consequently, this method might block or throw.

      Overrides:
      equals in class Object
      Implementation Requirements:
      The order of potential initialization triggering is specified as:
      1. this lazy constant
      2. obj lazy constant
      Parameters:
      obj - an object to be tested for equality (can be null)
      Returns:
      true if the other object is "equal to" this object otherwise false
      See Also:
    • hashCode

      int hashCode()
      Returns the hash code of this constant.

      This method may trigger initialization of this lazy constant. Consequently, this method might block or throw.

      Overrides:
      hashCode in class Object
      Returns:
      the hash code of this constant
      See Also:
    • toString

      String toString()
      Returns a non-initializing string suitable for debugging.

      This method never triggers initialization of this lazy constant and will observe initialization by other threads atomically (i.e., it observes the initialized value if and only if the initialization has already completed).

      If this lazy constant is initialized, the Object.toString() of the initialized constant will be returned, otherwise, an implementation dependent string is returned that indicates this lazy constant is not yet initialized.

      Overrides:
      toString in class Object
      Returns:
      a non-initializing string suitable for debugging
    • of

      static <T> LazyConstantPREVIEW<T> of(Supplier<? extends T> computingFunction)
      Returns a new lazy constant to be computed later via the provided computingFunction.

      The returned lazy constant strongly references the provided computingFunction until initialization completes successfully; after which the computing function is no longer strongly referenced and becomes eligible for garbage collection.

      Type Parameters:
      T - type of the constant
      Parameters:
      computingFunction - in the form of a Supplier to be used to compute the constant
      Returns:
      a new lazy constant to be computed later via the provided computingFunction