Interface StableValue<T>
- Type Parameters:
T- type of the contents
StableValue is a preview API of the Java platform.StableValue when preview features are enabled. 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"); // ... } }Component 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 runtimePowerOf2Util.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 runtimeLog2Util.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 runtimeSimilarly, 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 runtimeComposing 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(); } }bar() 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); } }FIB 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)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
trySet(Object)that returnstrue, - a
setOrThrow(Object)that does not throw, or - an
orElseSet(Supplier)that successfully runs the supplier
- a
orElseThrow()that does not throw, - a
orElse(other)that does not return theothervalue - an
orElseSet(Supplier)that does notthrow, or - an
isSet()that returnstrue
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 of
StableValueare free to synchronize onthisand consequently, it should be avoided to (directly or indirectly) synchronize on aStableValue. Hence, synchronizing onthismay lead to deadlock.Except for a
StableValue's contents itself, anorElse(other) parameter, and anequals(obj) parameter; all method parameters must benon-null or aNullPointerExceptionwill be thrown. - Implementation Note:
- A
StableValueis 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 provide
Object.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.
A
StableValuethat has a type parameterTthat 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 not
Serializable. - Since:
- 25
Method Summary
Modifier and TypeMethodDescriptionbooleanReturnstrueifthis == obj,falseotherwise.static <T,R> Function <T, R> Returns a new stableFunction.inthashCode()Returns theidentity hash code ofthisobject.static <R> IntFunction<R> intFunction(int size,IntFunction<? extends R> underlying) Returns a new stableIntFunction.booleanisSet()Returnstrueif the contents is set,falseotherwise.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> 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.Returns the contents if set, otherwise, returns the providedothervalue.Returns the contents; if unset, first attempts to compute and set the contents using the providedsupplier.Returns the contents if set, otherwise, throwsNoSuchElementException.voidsetOrThrow(T contents) Sets the contents of this StableValue to the providedcontents, or, ifalready set, throwsIllegalStateException.static <T> Supplier<T> Returns a new stable supplier.booleanTries to set the contents of this StableValue to the providedcontents.
Method Details
trySet
Tries to set the contents of this StableValue to the providedcontents.The contents of this StableValue can only be set once, implying this method onlyreturnstrueonce.When this method returns, the contents of this StableValue is always set.
- Parameters:
contents- to set- Returns:
trueif the contents of this StableValue was set to the providedcontents,falseotherwise- Throws:
IllegalStateException- if a supplier invoked byorElseSet(Supplier)recursively attempts to set this stable value by calling this method directly or indirectly.
orElse
orElseThrow
T orElseThrow()Returns the contents if set, otherwise, throwsNoSuchElementException.- Returns:
- the contents if set, otherwise, throws
NoSuchElementException - Throws:
NoSuchElementException- if no contents is set
isSet
boolean isSet()Returnstrueif the contents is set,falseotherwise.- Returns:
trueif the contents is set,falseotherwise
orElseSet
Returns the contents; if unset, first attempts to compute and set the contents using the providedsupplier.The provided
supplieris 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 provided
supplierwill only be invoked once even if invoked fromseveral threads unless thesupplierthrows 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 provided
supplier - Throws:
IllegalStateException- if the providedsupplierrecursively attempts to set this stable value.
setOrThrow
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 setIllegalStateException- if a supplier invoked byorElseSet(Supplier)recursively attempts to set this stable value by calling this method directly or indirectly.
equals
hashCode
int hashCode()Returns theidentity hash code ofthisobject.- Overrides:
hashCodein classObject- Returns:
- theidentity hash code of
thisobject - See Also:
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
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 provided
contents
supplier
Returns a new stable supplier.The returnedsupplier is a caching supplier that recordsthe value of the provided
underlyingsupplier upon being first accessed viathe returned supplier'sget() method.The provided
underlyingsupplier 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 theunderlyingsupplier.If the provided
underlyingsupplier throws an exception, it is rethrownto the initial caller and no contents is recorded.If the provided
underlyingsupplier 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
Returns a new stableIntFunction.The returned function is a caching function that, for each allowed
intinput, 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), anIllegalArgumentExceptionwill be thrown.The provided
underlyingfunction 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 provided
underlyingfunction throws an exception, it isrethrown to the initial caller and no contents is recorded.If the provided
underlyingfunction 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 inputsunderlying-IntFunctionused to compute cached values- Returns:
- a new stableIntFunction
- Throws:
IllegalArgumentException- if the providedsizeis 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 of
inputs, 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 ininputs, anIllegalArgumentExceptionwill be thrown.The provided
underlyingfunction 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 provided
underlyingfunction throws an exception, it isrethrown to the initial caller and no contents is recorded.If the provided
underlyingfunction recursively calls the returnedfunction for the same input, anIllegalStateException willbe thrown.- Type Parameters:
T- the type of the input to the returned FunctionR- the type of results delivered by the returned Function- Parameters:
inputs- the set of (non-null) allowed input valuesunderlying-Functionused to compute cached values- Returns:
- a new stableFunction
- Throws:
NullPointerException- if the provided set ofinputscontains anullelement.
list
Returns a new stable list with the providedsize.The returned list is anunmodifiable listwith the provided
size. The list's elements are computed via theprovidedmapperwhen they are first accessed(e.g. viaList::get).The provided
mapperfunction 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 provided
mapperfunction throws an exception, itis rethrown to the initial caller and no value for the element is recorded.Any
subListorList.reversed()viewsof the returned list are also stable.The returned list and its
subListorList.reversed()views implement theRandomAccessinterface.The returned list is unmodifiable and does not implement theoptional operations in theList interface.
If the provided
mapperrecursively 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 listmapper- to invoke whenever an element is first accessed (may returnnull)- Returns:
- a new stable list with the provided
size - Throws:
IllegalArgumentException- if the providedsizeis negative.
map
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 provided
mapperwhen they are first accessed(e.g. viaMap::get).The provided
mapperfunction 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 provided
mapperfunction throws an exception, itis rethrown to the initial caller and no value associated with the provided keyis recorded.Any
Map.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 provided
mapperrecursively calls the returned map forthe same key, anIllegalStateException will be thrown.- Type Parameters:
K- the type of keys maintained by the returned mapV- the type of mapped values in the returned map- Parameters:
keys- the (non-null) keys in the returned mapmapper- to invoke whenever an associated value is first accessed (may returnnull)- Returns:
- a new stable map with the provided
keys - Throws:
NullPointerException- if the provided set ofinputscontains anullelement.