- Type Parameters:
V
- The type of the value to be bound
- All Superinterfaces:
Supplier<V>
ComputedConstant
is a preview API of the Java platform.
A computed constant can be queried after being created to provide a bound value,
for example when get()
is invoked.
Once bound (or if it failed to bound), computed constant instances are guaranteed to
be lock free and are eligible for constant folding optimizations by the JVM.
Providers of constant values are guaranteed to be invoked at most one time. In other words,
a provider can only run once and in the first-calling thread and so, there is no race across threads
which guarantees the at-most-once evaluation. The life cycle of a computed constant is said
to be monotonic where it can go from the initial state of unbound
(when it is not associated with any value) eventually to the terminal state of bound (when it is
permanently associated with a fixed value). The value can be null
.
This contrasts with AtomicReference
where any number of
updates can be done and where there is no simple way to atomically compute a value
(guaranteed to only be computed once) if missing. Computed constants also contrasts to
Future
where a value is computed in another thread.
The implementations are optimized for providing high average performance for get operations over many invocations.
Computed Constant Factories
New instances of ComputedConstants can only be obtained via the following factory methods:- Single Instances
- ComputedConstant.of(Supplier<? extends V> provider) providing a single new ComputedConstant instance
- ComputedConstant.of(Class<? super V> type; MethodHandle provider) providing a single new ComputedConstant instance
- Collections
- ComputedConstant.of(int length, IntFunction<? super V> mappingProvider) providing a new List of ComputedConstant elements
ComputedConstant
ComputedConstant
provides atomic evaluation using a provider:
class DemoPreset {
private static final ComputedConstant<Foo> FOO = ComputedConstant.of(Foo::new); // provider = Foo::new
public Foo theFoo() {
// Foo is lazily constructed and recorded here upon first invocation
return FOO.get();
}
}
get()
method in the example above is on par with using an
inner/private class holding a lazily initialized variable but with no overhead imposed by
the extra holder class. Such a holder class might implement a lazy value as follows:
class DemoHolder {
public Foo theBar() {
class Holder {
private static final Foo FOO = new Foo();
}
// Foo is lazily constructed and recorded here upon first invocation
return Holder.FOO;
}
}
class DemoBackground {
private static final ComputedConstant<Foo> CONSTANT = ComputedConstant.of(Foo::new);
static {
Thread.ofVirtual().start(CONSTANT::get);
}
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000);
// CONSTANT is likely already pre-computed here by a background thread
System.out.println(CONSTANT.get());
}
}
ComputedConstant<T>
implements Supplier<T>
allowing simple
interoperability with legacy code and less specific type declaration
as shown in the following example:
class SupplierDemo {
// Eager Supplier of Foo
private static final Supplier<Foo> EAGER_FOO = Foo::new;
// Turns an eager Supplier into a caching constant Supplier
private static final Supplier<Foo> LAZILY_CACHED_FOO = ComputedConstant.of(EAGER_FOO);
public static void main(String[] args) {
// Lazily compute the one-and-only `Foo`
Foo theFoo = LAZILY_CACHED_FOO.get();
}
}
List of ComputedConstant Elements
Lists of ComputedConstant elements can also be obtained viaComputedConstant
factory
methods in a similar way as for ComputedConstant
instances but with an extra initial arity, indicating
the desired size of the List:
class DemoArray {
// 1. Declare a list of ComputedConstant elements of size 32
private static final List<ComputedConstant<Long>> VALUE_PO2_CACHE =
ComputedConstant.of(32, index -> 1L << index); // mappingProvider = index -> 1L << index
public long powerOfTwo(int n) {
// 2. The n:th slot is computed and bound here upon the
// first call of get(n). The other elements are not affected.
// 3. Using an n outside the list will throw an IndexOutOfBoundsException
return VALUE_PO2_CACHE.get(n).get();
}
}
IntFunction
rather
than a Supplier
, allowing custom values to be
computed and bound for elements in the list depending on the current index being used.
States
ComputedConstant
instances maintain an internal state described as follows:
- Unbound
- Indicates no value is bound (transient state)
- Can move to "Binding"
- This state can be detected using the method
isUnbound()
- Binding
- Indicates an attempt to bind a value is in progress (transient state)
- Can move to "Bound" or "Error"
- This state can be unreliably assumed using a combination of the "is" predicates (where all are false)
- Bound
- Indicates a value is successfully bound (final state)
- Cannot move
- This state can be detected using the method
isBound()
- Error
- Indicates an error when trying to bind a value (final state)
- Cannot move
- This state can be detected using the method
isError()
The internal states and their transitions are depicted below where gray nodes indicate final states:
General Properties of ComputedConstant
All methods of this class will throw aNullPointerException
if a reference parameter is null
unless otherwise specified.
All computed constant constructs are "null-friendly" meaning a value can be bound to null
. As usual, values of type
Optional may also express optionality, without using null
, as exemplified here:
class NullDemo {
private Supplier<Optional<Color>> backgroundColor =
ComputedConstant.of(() -> Optional.ofNullable(calculateBgColor()));
Color backgroundColor(Color defaultColor) {
return backgroundColor.get()
.orElse(defaultColor);
}
private Color calculateBgColor() {
// Read background color from file returning "null" if it fails.
// ...
return null;
}
}
- Since:
- 22
-
Method Summary
Modifier and TypeMethodDescriptionget()
Returns the bound value of this computed constant. If no value is bound, atomically attempts to compute and record a bound value using the pre-set provider.boolean
isBound()
Returnstrue
if a value is bound to this constant.boolean
isError()
Returnstrue
if an attempt was made to bind a value but a value could not be bound to this constant.boolean
Returnstrue
if no attempt has been made to bind a value to this constant.default <R> ComputedConstantPREVIEW
<R> Returns a newComputedConstant
PREVIEW that will use this computed constant's eventually bound value and then apply the providedmapper
as the pre-set provider.static <V> List
<ComputedConstantPREVIEW<V>> of
(int size, IntFunction<? extends V> mappingProvider) Returns a new unmodifiable List ofComputedConstant
PREVIEW elements with the providedsize
and given pre-setmappingProvider
to be used to compute element values.static <V,
T extends V>
ComputedConstantPREVIEW<V> of
(Class<T> superType, MethodHandle provider) Returns a newComputedConstant
PREVIEW with the given pre-setprovider
to be used to compute a value and providedsuperType
indicating the most specific return type that can be expressed.static <V> ComputedConstantPREVIEW
<V> Returns a newComputedConstant
PREVIEW with the given pre-setprovider
to be used to compute a value.Returns the bound value of this computed constant. If no value is bound, atomically attempts to compute and record a bound value using the pre-set provider or, if the provider throws an unchecked exception, returns the providedother
value.orElseThrow
(Supplier<? extends X> exceptionSupplier) Returns the bound value of this computed constant. If no value is bound, atomically attempts to compute and record a bound value using the pre-set provider or, if the provider throws an unchecked exception, throws an exception produced by invoking the providedexceptionSupplier
function.
-
Method Details
-
isUnbound
boolean isUnbound()Returnstrue
if no attempt has been made to bind a value to this constant.- Returns:
true
if no attempt has been made to bind a value to this constant
-
isBound
boolean isBound()Returnstrue
if a value is bound to this constant.- Returns:
true
if a value is bound to this constant
-
isError
boolean isError()Returnstrue
if an attempt was made to bind a value but a value could not be bound to this constant.- Returns:
true
if an attempt was made to bind a value but a value could not be bound to this constant
-
get
V get()Returns the bound value of this computed constant. If no value is bound, atomically attempts to compute and record a bound value using the pre-set provider.If the provider returns
null
,null
is bound and returned. If the provider throws an (unchecked) exception, the exception is wrapped into aNoSuchElementException
which is thrown, and no value is bound. If an Error is thrown by the provider, the Error is relayed to the caller. If an Exception or an Error is thrown by the provider, no further attempt is made to bind the value and all subsequent invocations of this method will throw a newNoSuchElementException
.The most common usage is to construct a new object serving as a memoized result, as in:
ComputedConstant<V> constant = ComputedConstant.of(Value::new); // ... V value = constant.get(); assertNotNull(value); // Value is non-null
If a thread calls this method while being bound by another thread, the current thread will be suspended until the binding completes (successfully or not). Otherwise, this method is guaranteed to be lock-free.
- Specified by:
get
in interfaceSupplier<V>
- Returns:
- the bound value of this computed constant. If no value is bound, atomically attempts to compute and record a bound value using the pre-set provider
- Throws:
NoSuchElementException
- if a value cannot be boundStackOverflowError
- if a circular dependency is detected (i.e. the provider calls itself directly or indirectly in the same thread).Error
- if the provider throws an Error
-
orElse
Returns the bound value of this computed constant. If no value is bound, atomically attempts to compute and record a bound value using the pre-set provider or, if the provider throws an unchecked exception, returns the providedother
value.If a thread calls this method while being bound by another thread, the current thread will be suspended until the binding completes (successfully or not). Otherwise, this method is guaranteed to be lock-free.
- Parameters:
other
- to use if no value neither is bound nor can be bound (can be null)- Returns:
- the bound value of this computed constant. If no value is bound, atomically attempts
to compute and record a bound value using the pre-set provider
or, if the provider throws an unchecked exception, returns the provided
other
value - Throws:
NoSuchElementException
- if a value cannot be boundStackOverflowError
- if a circular dependency is detected (i.e. the provider calls itself directly or indirectly in the same thread).Error
- if the provider throws an Error
-
orElseThrow
Returns the bound value of this computed constant. If no value is bound, atomically attempts to compute and record a bound value using the pre-set provider or, if the provider throws an unchecked exception, throws an exception produced by invoking the providedexceptionSupplier
function.If a thread calls this method while being bound by another thread, the current thread will be suspended until the binding completes (successfully or not). Otherwise, this method is guaranteed to be lock-free.
- Type Parameters:
X
- the type of the exception that may be thrown- Parameters:
exceptionSupplier
- the supplying function that produces the exception to throw- Returns:
- the bound value of this computed constant. If no value is bound, atomically attempts
to compute and record a bound value using the pre-set provider
or, if the provider throws an unchecked exception, throws an exception produced by invoking the
provided
exceptionSupplier
function - Throws:
X
- if a value cannot be bound.Error
- if the provider throws an Error
-
map
Returns a newComputedConstant
PREVIEW that will use this computed constant's eventually bound value and then apply the providedmapper
as the pre-set provider.- Type Parameters:
R
- the return type of the providedmapper
- Parameters:
mapper
- to apply to this computed constant- Returns:
- a new
ComputedConstant
PREVIEW that will use this computed constant's eventually bound value and then apply the providedmapper
as the pre-set provider
-
of
Returns a newComputedConstant
PREVIEW with the given pre-setprovider
to be used to compute a value.If a later attempt is made to invoke any of the
get()
,orElse(Object)
ororElseThrow(Supplier)
methods when this computed constant is unbound, theprovider
will automatically be invoked.class DemoPreset { private static final ComputedConstant<Foo> FOO = ComputedConstant.of(Foo::new); public Foo theBar() { // Foo is lazily constructed and recorded here upon first invocation return FOO.get(); } }
- Type Parameters:
V
- the type of the value- Parameters:
provider
- to invoke when computing a value- Returns:
- a new
ComputedConstant
PREVIEW with the given pre-setprovider
to be used to compute a value
-
of
Returns a newComputedConstant
PREVIEW with the given pre-setprovider
to be used to compute a value and providedsuperType
indicating the most specific return type that can be expressed.If a later attempt is made to invoke any of the
get()
,orElse(Object)
ororElseThrow(Supplier)
methods when this computed constant is unbound, theprovider
will automatically be invoked.class DemoPreset { private static final ComputedConstant<Foo> FOO = ComputedConstant.of(createFooMH()); public Foo theBar() { // Foo is lazily constructed and recorded here upon first invocation return FOO.get(); } }
- Type Parameters:
V
- the type of the valueT
- a superclass of the value- Parameters:
superType
- a class indicating the most specific return type (that can be expressed) of the provider (which is used for type inference and method handle validation)provider
- to invoke when computing a value- Returns:
- a new
ComputedConstant
PREVIEW with the given pre-setprovider
to be used to compute a value and providedsuperType
indicating the most specific return type that can be expressed - Throws:
IllegalArgumentException
- if the given MethodHandleprovider
has a call signature return type that is not assignable from the providedsuperType
or if it takes any parameters.
-
of
Returns a new unmodifiable List ofComputedConstant
PREVIEW elements with the providedsize
and given pre-setmappingProvider
to be used to compute element values.The List and its elements are eligible for constant folding optimizations by the JVM.
Below, an example of how to cache values in a list is shown:
class DemoList { private static final List<ComputedConstant<Long>> PO2_CACHE = ComputedConstant.of(32, index -> 1L << index); public long powerOfTwoValue(int n) { return PO2_CACHE.get(n); } }
- API Note:
- The list is free to return value-based ComputedConstant elements that has no valid identity.
- Type Parameters:
V
- the type of the values- Parameters:
size
- the size of the ListmappingProvider
- to invoke when computing and binding element values- Returns:
- a new unmodifiable List of
ComputedConstant
PREVIEW elements with the providedsize
and given pre-setmappingProvider
to be used to compute element values
-
ComputedConstant
when preview features are enabled.