Interface StableValue<T>

Type Parameters:
T - type of the contents

public sealed interfaceStableValue<T>
StableValue is a preview API of the Java platform.
Programs can only useStableValue when preview features are enabled.
Preview features may be removed in a future release, or upgraded to permanent features of the Java platform.
A stable value is a holder of contents that can be set at most once.

AStableValue<T> is typically created using the factory methodStableValue.of(). When created this way, the stable value isunset, which means it holds nocontents. Its contents, of typeT, can beset by callingtrySet(),setOrThrow(), ororElseSet(). Once set, the contents can never change and can be retrieved by callingorElseThrow() ,orElse(), ororElseSet().

Consider the following example where a stable value field "logger" is a shallowly immutable holder of contents of typeLogger and that is initially created asunset, which means it holds no contents. Later in the example, the state of the "logger" field is checked and if it is stillunset, the contents isset:

 public class Component {    // Creates a new unset stable value with no contents    private final StableValue<Logger> logger = StableValue.of();    private Logger getLogger() {        if (!logger.isSet()) {            logger.trySet(Logger.create(Component.class));        }        return logger.orElseThrow();    }    public void process() {        getLogger().info("Process started");        // ...    } }

IfgetLogger() is called from several threads, several instances ofLogger might be created. However, the contents can only be set at most once meaning the first writer wins.

In order to guarantee that, even under races, only one instance ofLogger is ever created, theorElseSet() method can be used instead, where the contents are lazily computed, and atomically set, via asupplier. In the example below, the supplier is provided in the form of a lambda expression:

 public class Component {    // Creates a new unset stable value with no contents    private final StableValue<Logger> logger = StableValue.of();    private Logger getLogger() {        return logger.orElseSet( () -> Logger.create(Component.class) );    }    public void process() {        getLogger().info("Process started");        // ...    } }

ThegetLogger() method callslogger.orElseSet() on the stable value to retrieve its contents. If the stable value isunset, thenorElseSet() evaluates the given supplier, and sets the contents to the result; the result is then returned to the client. In other words,orElseSet() guarantees that a stable value's contents isset before it returns.

Furthermore,orElseSet() guarantees that out of one or more suppliers provided, only at most one is ever evaluated, and that one is only ever evaluated once, even whenlogger.orElseSet() is invoked concurrently. This property is crucial as evaluation of the supplier may have side effects, for example, the call above toLogger.create() may result in storage resources being prepared.

Stable Functions

Stable values provide the foundation for higher-level functional abstractions. Astable supplier is a supplier that computes a value and then caches it into a backing stable value storage for subsequent use. A stable supplier is created via theStableValue.supplier() factory, by providing an underlyingSupplier which is invoked when the stable supplier is first accessed:
 public class Component {     private final Supplier<Logger> logger =             StableValue.supplier( () -> Logger.getLogger(Component.class) );     public void process() {        logger.get().info("Process started");        // ...     } }
A stable supplier encapsulates access to its backing stable value storage. This means that code insideComponent can obtain the logger object directly from the stable supplier, without having to go through an accessor method likegetLogger().

Astable int function is a function that takes anint parameter and uses it to compute a result that is then cached by the backing stable value storage for that parameter value. A stableIntFunction is created via theStableValue.intFunction() factory. Upon creation, the input range (i.e.[0, size)) is specified together with an underlyingIntFunction which is invoked at most once per input value. In effect, the stable int function will act like a cache for the underlyingIntFunction:

 final class PowerOf2Util {     private PowerOf2Util() {}     private static final int SIZE = 6;     private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =         v -> 1 << v;     private static final IntFunction<Integer> POWER_OF_TWO =         StableValue.intFunction(SIZE, UNDERLYING_POWER_OF_TWO);     public static int powerOfTwo(int a) {         return POWER_OF_TWO.apply(a);     } } int result = PowerOf2Util.powerOfTwo(4);   // May eventually constant fold to 16 at runtime
ThePowerOf2Util.powerOfTwo() function is apartial function that only allows a subset[0, 5] of the underlying function'sUNDERLYING_POWER_OF_TWO input range.

Astable function is a function that takes a parameter (of typeT) and uses it to compute a result (of typeR) that is then cached by the backing stable value storage for that parameter value. A stable function is created via theStableValue.function() factory. Upon creation, the inputSet is specified together with an underlyingFunction which is invoked at most once per input value. In effect, the stable function will act like a cache for the underlyingFunction:

 class Log2Util {     private Log2Util() {}     private static final Set<Integer> KEYS =         Set.of(1, 2, 4, 8, 16, 32);     private static final UnaryOperator<Integer> UNDERLYING_LOG2 =         i -> 31 - Integer.numberOfLeadingZeros(i);     private static final Function<Integer, Integer> LOG2 =         StableValue.function(KEYS, UNDERLYING_LOG2);     public static int log2(int a) {         return LOG2.apply(a);     } } int result = Log2Util.log2(16);   // May eventually constant fold to 4 at runtime
TheLog2Util.log2() function is apartial function that only allows a subset{1, 2, 4, 8, 16, 32} of the underlying function'sUNDERLYING_LOG2 input range.

Stable Collections

Stable values can also be used as backing storage forunmodifiable collections. Astable list is an unmodifiable list, backed by an array of stable values. The stable list elements are computed when they are first accessed, using a providedIntFunction:
final class PowerOf2Util {    private PowerOf2Util() {}    private static final int SIZE = 6;    private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =            v -> 1 << v;    private static final List<Integer> POWER_OF_TWO =        StableValue.list(SIZE, UNDERLYING_POWER_OF_TWO);    public static int powerOfTwo(int a) {        return POWER_OF_TWO.get(a);    }}int result = PowerOf2Util.powerOfTwo(4);   // May eventually constant fold to 16 at runtime

Similarly, astable map is an unmodifiable map whose keys are known at construction. The stable map values are computed when they are first accessed, using a providedFunction:

 class Log2Util {     private Log2Util() {}     private static final Set<Integer> KEYS =         Set.of(1, 2, 4, 8, 16, 32);     private static final UnaryOperator<Integer> UNDERLYING_LOG2 =         i -> 31 - Integer.numberOfLeadingZeros(i);     private static final Map<Integer, INTEGER> LOG2 =         StableValue.map(CACHED_KEYS, UNDERLYING_LOG2);     public static int log2(int a) {          return LOG2.get(a);     } } int result = Log2Util.log2(16);   // May eventually constant fold to 4 at runtime

Composing stable values

A stable value can depend on other stable values, forming a dependency graph that can be lazily computed but where access to individual elements can still be performant. In the following example, a singleFoo and aBar instance (that is dependent on theFoo instance) are lazily created, both of which are held by stable values:
 public final class DependencyUtil {     private DependencyUtil() {}     public static class Foo {          // ...      }     public static class Bar {         public Bar(Foo foo) {              // ...         }     }     private static final Supplier<Foo> FOO = StableValue.supplier(Foo::new);     private static final Supplier<Bar> BAR = StableValue.supplier(() -> new Bar(FOO.get()));     public static Foo foo() {         return FOO.get();     }     public static Bar bar() {         return BAR.get();     } }
Callingbar() will create theBar singleton if it is not already created. Upon such a creation, the dependentFoo will first be created if theFoo does not already exist.

Another example, which has a more complex dependency graph, is to compute the Fibonacci sequence lazily:

 public final class Fibonacci {     private Fibonacci() {}     private static final int MAX_SIZE_INT = 46;     private static final IntFunction<Integer> FIB =         StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib);     public static int fib(int n) {         return n < 2                 ? n                 : FIB.apply(n - 1) + FIB.apply(n - 2);     } }
BothFIB andFibonacci::fib recurse into each other. Because the stable int functionFIB caches intermediate results, the initial computational complexity is reduced from exponential to linear compared to a traditional non-caching recursive fibonacci method. Once computed, the VM is free to constant-fold expressions likeFibonacci.fib(5).

The fibonacci example above is a directed acyclic graph (i.e., it has no circular dependencies and is therefore a dependency tree):

              ___________fib(5)____________             /                             \       ____fib(4)____                  ____fib(3)____      /              \                /              \    fib(3)          fib(2)          fib(2)          fib(1)   /     \         /     \         /     \ fib(2) fib(1)   fib(1) fib(0)   fib(1) fib(0)
If there are circular dependencies in a dependency graph, a stable value will eventually throw anIllegalStateException upon referencing elements in a circularity.

Thread Safety

The contents of a stable value is guaranteed to be set at most once. If competing threads are racing to set a stable value, only one update succeeds, while the other updates are blocked until the stable value is set, whereafter the other updates observes the stable value is set and leave the stable value unchanged.

The at-most-once write operation on a stable value that succeeds (e.g.trySet())happens-before any successful read operation (e.g.orElseThrow()). A successful write operation can be either:

A successful read operation can be either:

The methodorElseSet(Supplier) guarantees that the providedSupplier is invoked successfully at most once, even under race. Invocations oforElseSet(Supplier) form a total order of zero or more exceptional invocations followed by zero (if the contents were already set) or one successful invocation. Since stable functions and stable collections are built on top of the same principles asorElseSet() they too are thread safe and guarantee at-most-once-per-input invocation.

Performance

As the contents of a stable value can never change after it has been set, a JVM implementation may, for a set stable value, elide all future reads of that stable value, and instead directly use any contents that it has previously observed. This is true if the reference to the stable value is a constant (e.g. in cases where the stable value itself is stored in astatic final field). Stable functions and collections are built on top of StableValue. As such, they might also be eligible for the same JVM optimizations as for StableValue.
Implementation Requirements:
Implementing classes ofStableValue are free to synchronize onthis and consequently, it should be avoided to (directly or indirectly) synchronize on aStableValue. Hence, synchronizing onthis may lead to deadlock.

Except for aStableValue's contents itself, anorElse(other) parameter, and anequals(obj) parameter; all method parameters must benon-null or aNullPointerException will be thrown.

Implementation Note:
AStableValue is mainly intended to be a non-public field in a class and is usually neither exposed directly via accessors nor passed as a method parameter.

Stable functions and collections make reasonable efforts to provideObject.toString() operations that do not trigger evaluation of the internal stable values when called. Stable collections haveObject.equals(Object) operations that try to minimize evaluation of the internal stable values when called.

As objects can be set via stable values but never removed, this can be a source of unintended memory leaks. A stable value's contents arestrongly reachable. Be advised that reachable stable values will hold their set contents until the stable value itself is collected.

AStableValue that has a type parameterT that is an array type (of arbitrary rank) will only allow the JVM to treat thearray reference as a stable value butnot its components. Instead, aa stable list of arbitrary depth can be used, which provides stable components. More generally, a stable value can hold other stable values of arbitrary depth and still provide transitive constantness.

Stable values, functions, and collections are notSerializable.

Since:
25
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    Returnstrue ifthis == obj,false otherwise.
    static <T,R> Function<T,R>
    function(Set<? extends T> inputs,Function<? super T, ? extends R> underlying)
    Returns a new stableFunction.
    int
    Returns theidentity hash code ofthis object.
    static <R> IntFunction<R>
    intFunction(int size,IntFunction<? extends R> underlying)
    Returns a new stableIntFunction.
    boolean
    Returnstrue if the contents is set,false otherwise.
    static <E> List<E>
    list(int size,IntFunction<? extends E> mapper)
    Returns a new stable list with the providedsize.
    static <K,V> Map<K,V>
    map(Set<K> keys,Function<? super K, ? extends V> mapper)
    Returns a new stable map with the providedkeys.
    static <T> StableValuePREVIEW<T>
    of()
    Returns a new unset stable value.
    static <T> StableValuePREVIEW<T>
    of(T contents)
    Returns a new pre-set stable value with the providedcontents.
    orElse(T other)
    Returns the contents if set, otherwise, returns the providedother value.
    orElseSet(Supplier<? extendsT> supplier)
    Returns the contents; if unset, first attempts to compute and set the contents using the providedsupplier.
    Returns the contents if set, otherwise, throwsNoSuchElementException.
    void
    setOrThrow(T contents)
    Sets the contents of this StableValue to the providedcontents, or, ifalready set, throwsIllegalStateException.
    static <T> Supplier<T>
    supplier(Supplier<? extends T> underlying)
    Returns a new stable supplier.
    boolean
    trySet(T contents)
    Tries to set the contents of this StableValue to the providedcontents.
  • Method Details

    • trySet

      boolean trySet(T contents)
      Tries to set the contents of this StableValue to the providedcontents.The contents of this StableValue can only be set once, implying this method onlyreturnstrue once.

      When this method returns, the contents of this StableValue is always set.

      Parameters:
      contents - to set
      Returns:
      true if the contents of this StableValue was set to the providedcontents,false otherwise
      Throws:
      IllegalStateException - if a supplier invoked byorElseSet(Supplier) recursively attempts to set this stable value by calling this method directly or indirectly.
    • orElse

      T orElse(T other)
      Returns the contents if set, otherwise, returns the providedother value.
      Parameters:
      other - to return if the contents is not set
      Returns:
      the contents if set, otherwise, returns the providedother value
    • orElseThrow

      T orElseThrow()
      Returns the contents if set, otherwise, throwsNoSuchElementException.
      Returns:
      the contents if set, otherwise, throwsNoSuchElementException
      Throws:
      NoSuchElementException - if no contents is set
    • isSet

      boolean isSet()
      Returnstrue if the contents is set,false otherwise.
      Returns:
      true if the contents is set,false otherwise
    • orElseSet

      T orElseSet(Supplier<? extendsT> supplier)
      Returns the contents; if unset, first attempts to compute and set the contents using the providedsupplier.

      The providedsupplier is guaranteed to be invoked at most once if itcompletes without throwing an exception. If this method is invoked several timeswith different suppliers, only one of them will be invoked provided it completeswithout throwing an exception.

      If the supplier throws an (unchecked) exception, the exception is rethrown and nocontents is set. The most common usage is to construct a new object servingas a lazily computed value or memoized result, as in:

      Value v = stable.orElseSet(Value::new);

      When this method returns successfully, the contents is always set.

      The providedsupplier will only be invoked once even if invoked fromseveral threads unless thesupplier throws an exception.

      Parameters:
      supplier - to be used for computing the contents, if not previously set
      Returns:
      the contents; if unset, first attempts to compute and set the contents using the providedsupplier
      Throws:
      IllegalStateException - if the providedsupplier recursively attempts to set this stable value.
    • setOrThrow

      void setOrThrow(T contents)
      Sets the contents of this StableValue to the providedcontents, or, ifalready set, throwsIllegalStateException.

      When this method returns (or throws an exception), the contents is always set.

      Parameters:
      contents - to set
      Throws:
      IllegalStateException - if the contents was already set
      IllegalStateException - if a supplier invoked byorElseSet(Supplier) recursively attempts to set this stable value by calling this method directly or indirectly.
    • equals

      boolean equals(Object obj)
      Returnstrue ifthis == obj,false otherwise.
      Overrides:
      equals in class Object
      Parameters:
      obj - to check for equality
      Returns:
      true ifthis == obj,false otherwise
      See Also:
    • hashCode

      int hashCode()
      Returns theidentity hash code ofthis object.
      Overrides:
      hashCode in class Object
      Returns:
      theidentity hash code ofthis object
      See Also:
    • of

      static <T> StableValuePREVIEW<T> of()
      Returns a new unset stable value.

      An unset stable value has no contents.

      Type Parameters:
      T - type of the contents
      Returns:
      a new unset stable value
    • of

      static <T> StableValuePREVIEW<T> of(T contents)
      Returns a new pre-set stable value with the providedcontents.
      Type Parameters:
      T - type of the contents
      Parameters:
      contents - to set
      Returns:
      a new pre-set stable value with the providedcontents
    • supplier

      static <T> Supplier<T> supplier(Supplier<? extends T> underlying)
      Returns a new stable supplier.

      The returnedsupplier is a caching supplier that recordsthe value of the providedunderlying supplier upon being first accessed viathe returned supplier'sget() method.

      The providedunderlying supplier is guaranteed to be successfully invokedat most once even in a multi-threaded environment. Competing threads invoking thereturned supplier'sget() method when a value isalready under computation will block until a value is computed or an exception isthrown by the computing thread. The competing threads will then observe the newlycomputed value (if any) and will then never execute theunderlying supplier.

      If the providedunderlying supplier throws an exception, it is rethrownto the initial caller and no contents is recorded.

      If the providedunderlying supplier recursively calls the returnedsupplier, anIllegalStateException will be thrown.

      Type Parameters:
      T - the type of results supplied by the returned supplier
      Parameters:
      underlying - supplier used to compute a cached value
      Returns:
      a new stable supplier
    • intFunction

      static <R> IntFunction<R> intFunction(int size,IntFunction<? extends R> underlying)
      Returns a new stableIntFunction.

      The returned function is a caching function that, for each allowedintinput, records the values of the providedunderlyingfunction upon being first accessed via the returned function'sapply() method. If the returned function isinvoked with an input that is not in the range[0, size), anIllegalArgumentException will be thrown.

      The providedunderlying function is guaranteed to be successfully invokedat most once per allowed input, even in a multi-threaded environment. Competingthreads invoking the returned function'sapply() method when a value is already undercomputation will block until a value is computed or an exception is thrown bythe computing thread.

      If invoking the providedunderlying function throws an exception, it isrethrown to the initial caller and no contents is recorded.

      If the providedunderlying function recursively calls the returnedfunction for the same input, anIllegalStateException willbe thrown.

      Type Parameters:
      R - the type of results delivered by the returned IntFunction
      Parameters:
      size - the upper bound of the range[0, size) indicating the allowed inputs
      underlying -IntFunction used to compute cached values
      Returns:
      a new stableIntFunction
      Throws:
      IllegalArgumentException - if the providedsize is negative.
    • function

      static <T,R> Function<T,R> function(Set<? extends T> inputs,Function<? super T, ? extends R> underlying)
      Returns a new stableFunction.

      The returned function is a caching function that, for each allowedinput in the given set ofinputs, records the values of the providedunderlying function upon being first accessed via the returned function'sapply() method. If the returned function isinvoked with an input that is not ininputs, anIllegalArgumentExceptionwill be thrown.

      The providedunderlying function is guaranteed to be successfully invokedat most once per allowed input, even in a multi-threaded environment. Competingthreads invoking the returned function'sapply()method when a value is already under computation will block until a value iscomputed or an exception is thrown by the computing thread.

      If invoking the providedunderlying function throws an exception, it isrethrown to the initial caller and no contents is recorded.

      If the providedunderlying function recursively calls the returnedfunction for the same input, anIllegalStateException willbe thrown.

      Type Parameters:
      T - the type of the input to the returned Function
      R - the type of results delivered by the returned Function
      Parameters:
      inputs - the set of (non-null) allowed input values
      underlying -Function used to compute cached values
      Returns:
      a new stableFunction
      Throws:
      NullPointerException - if the provided set ofinputs contains anull element.
    • list

      static <E> List<E> list(int size,IntFunction<? extends E> mapper)
      Returns a new stable list with the providedsize.

      The returned list is anunmodifiable listwith the providedsize. The list's elements are computed via theprovidedmapper when they are first accessed(e.g. viaList::get).

      The providedmapper function is guaranteed to be successfully invokedat most once per list index, even in a multi-threaded environment. Competingthreads accessing an element already under computation will block until an elementis computed or an exception is thrown by the computing thread.

      If invoking the providedmapper function throws an exception, itis rethrown to the initial caller and no value for the element is recorded.

      AnysubList orList.reversed() viewsof the returned list are also stable.

      The returned list and itssubList orList.reversed() views implement theRandomAccess interface.

      The returned list is unmodifiable and does not implement theoptional operations in theList interface.

      If the providedmapper recursively calls the returned list for thesame index, anIllegalStateException will be thrown.

      Type Parameters:
      E - the type of elements in the returned list
      Parameters:
      size - the size of the returned list
      mapper - to invoke whenever an element is first accessed (may returnnull)
      Returns:
      a new stable list with the providedsize
      Throws:
      IllegalArgumentException - if the providedsize is negative.
    • map

      static <K,V> Map<K,V> map(Set<K> keys,Function<? super K, ? extends V> mapper)
      Returns a new stable map with the providedkeys.

      The returned map is anunmodifiable map whosekeys are known at construction. The map's values are computed via the providedmapper when they are first accessed(e.g. viaMap::get).

      The providedmapper function is guaranteed to be successfully invokedat most once per key, even in a multi-threaded environment. Competingthreads accessing a value already under computation will block until an elementis computed or an exception is thrown by the computing thread.

      If invoking the providedmapper function throws an exception, itis rethrown to the initial caller and no value associated with the provided keyis recorded.

      AnyMap.values() orMap.entrySet() views of the returned map arealso stable.

      The returned map is unmodifiable and does not implement theoptional operations in theMap interface.

      If the providedmapper recursively calls the returned map forthe same key, anIllegalStateException will be thrown.

      Type Parameters:
      K - the type of keys maintained by the returned map
      V - the type of mapped values in the returned map
      Parameters:
      keys - the (non-null) keys in the returned map
      mapper - to invoke whenever an associated value is first accessed (may returnnull)
      Returns:
      a new stable map with the providedkeys
      Throws:
      NullPointerException - if the provided set ofinputs contains anull element.