Add lazy-constants module: LazyConstant (JEP 531) against the holder idiom and double-checked locking

Basics, failure semantics on 26 vs 27, lazy collections, API across 25/26/27, preview-flag traps and a JMH read-path benchmark (jars fetched and sha1-checked, not committed).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
This commit is contained in:
Claude
2026-09-24 12:27:27 +00:00
parent af44e59e7f
commit 814c32b5ce
25 changed files with 606 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
$ javac src/OldWays.java && java OldWays (21.0.10+7-Ubuntu-124.04)
[init] building Expensive via eager static final
main starts (the eager value already exists)
first holder() call:
[init] building Expensive via holder idiom
second holder() call:
first doubleChecked() call:
[init] building Expensive via double-checked locking
second doubleChecked() call:
$ javac src/OldWays.java && java OldWays (25.0.4.1+1-LTS)
[init] building Expensive via eager static final
main starts (the eager value already exists)
first holder() call:
[init] building Expensive via holder idiom
second holder() call:
first doubleChecked() call:
[init] building Expensive via double-checked locking
second doubleChecked() call:
$ javac src/OldWays.java && java OldWays (27+35)
[init] building Expensive via eager static final
main starts (the eager value already exists)
first holder() call:
[init] building Expensive via holder idiom
second holder() call:
first doubleChecked() call:
[init] building Expensive via double-checked locking
second doubleChecked() call:
+20
View File
@@ -0,0 +1,20 @@
$ javac --enable-preview --release 27 src/Basics.java && java --enable-preview Basics (27+35)
supplier calls before get: 0
get() returns: loaded
get() again: loaded
supplier calls after two gets: 1
16 threads asked at once, supplier calls: 1
$ javac --enable-preview --release 27 src/ToStringStates.java && java --enable-preview ToStringStates (27+35)
before get: [computing function=ToStringStates$$Lambda]
after get: [loaded]
after failed get: [failed with=java.lang.IllegalStateException]
$ javac --enable-preview --release 27 src/Service.java && java --enable-preview Service (27+35)
service created
[load] reading configuration
url: https://example.test
url again: https://example.test
[compile] building the e-mail pattern
isEmail([email protected]): true
isEmail(nope): false
+13
View File
@@ -0,0 +1,13 @@
$ javac --enable-preview --release 26 src/Failures.java && java --enable-preview Failures (26.0.2.1+1)
get #1: java.lang.IllegalStateException: boom | cause=null | supplier calls=1
get #2: java.lang.IllegalStateException: boom | cause=null | supplier calls=2
get #3: java.lang.IllegalStateException: boom | cause=null | supplier calls=3
null result: java.lang.NullPointerException | cause=null
recursion: java.lang.IllegalStateException: Recursive invocation of a LazyConstant's computing function: Failures$$Lambda | cause=null
$ javac --enable-preview --release 27 src/Failures.java && java --enable-preview Failures (27+35)
get #1: java.util.NoSuchElementException: Unable to access the constant because java.lang.IllegalStateException was thrown at initial computation | cause=java.lang.IllegalStateException: boom | supplier calls=1
get #2: java.util.NoSuchElementException: Unable to access the constant because java.lang.IllegalStateException was thrown at initial computation | cause=null | supplier calls=1
get #3: java.util.NoSuchElementException: Unable to access the constant because java.lang.IllegalStateException was thrown at initial computation | cause=null | supplier calls=1
null result: java.util.NoSuchElementException: Unable to access the constant because java.lang.NullPointerException was thrown at initial computation | cause=java.lang.NullPointerException
recursion: java.util.NoSuchElementException: Unable to access the constant because java.lang.IllegalStateException was thrown at initial computation | cause=java.lang.IllegalStateException: Recursive invocation of a LazyConstant's computing function: Failures$$Lambda
+26
View File
@@ -0,0 +1,26 @@
$ javac --enable-preview --release 27 src/LazyCollections.java && java --enable-preview LazyCollections (27+35)
list created, size 3 (nothing computed yet)
computing list element 1
list.get(1) = v1
list.get(1) again = v1
map created, size 3 (nothing computed yet)
computing map value for bb
map.get("bb") = 2
map.get("bb") again = 2
set created
testing 2
set.contains(2) = true
testing 1
set.contains(1) = false
set.contains(9) = false
testing 3
set.size() = 2
$ javac --enable-preview --release 26 src/LazyCollections.java (26.0.2.1+1)
src/LazyCollections.java:18: error: cannot find symbol
Set<Integer> set = Set.ofLazy(Set.of(1, 2, 3), n -> { System.out.println(" testing " + n); return n > 1; });
^
symbol: method ofLazy(Set<Integer>,(n)->{ Sys[...] 1; })
location: interface Set
1 error
exit=1
@@ -0,0 +1,118 @@
$ javap java.lang.StableValue (25.0.4.1+1-LTS)
Compiled from "StableValue.java"
public interface java.lang.StableValue<T> {
public abstract boolean trySet(T);
public abstract T orElse(T);
public abstract T orElseThrow();
public abstract boolean isSet();
public abstract T orElseSet(java.util.function.Supplier<? extends T>);
public abstract void setOrThrow(T);
public abstract boolean equals(java.lang.Object);
public abstract int hashCode();
public static <T> java.lang.StableValue<T> of();
public static <T> java.lang.StableValue<T> of(T);
public static <T> java.util.function.Supplier<T> supplier(java.util.function.Supplier<? extends T>);
public static <R> java.util.function.IntFunction<R> intFunction(int, java.util.function.IntFunction<? extends R>);
public static <T, R> java.util.function.Function<T, R> function(java.util.Set<? extends T>, java.util.function.Function<? super T, ? extends R>);
public static <E> java.util.List<E> list(int, java.util.function.IntFunction<? extends E>);
public static <K, V> java.util.Map<K, V> map(java.util.Set<K>, java.util.function.Function<? super K, ? extends V>);
}
$ javap java.lang.LazyConstant (26.0.2.1+1)
Compiled from "LazyConstant.java"
public interface java.lang.LazyConstant<T> extends java.util.function.Supplier<T> {
public abstract T orElse(T);
public abstract T get();
public abstract boolean isInitialized();
public abstract boolean equals(java.lang.Object);
public abstract int hashCode();
public abstract java.lang.String toString();
public static <T> java.lang.LazyConstant<T> of(java.util.function.Supplier<? extends T>);
}
$ javap java.lang.LazyConstant (27+35)
Compiled from "LazyConstant.java"
public interface java.lang.LazyConstant<T> extends java.util.function.Supplier<T> {
public abstract T get();
public abstract boolean equals(java.lang.Object);
public abstract int hashCode();
public abstract java.lang.String toString();
public static <T> java.lang.LazyConstant<T> of(java.util.function.Supplier<? extends T>);
}
$ javap java.util.List | grep ofLazy (26.0.2.1+1)
public static <E> java.util.List<E> ofLazy(int, java.util.function.IntFunction<? extends E>);
$ javap java.util.Map | grep ofLazy (26.0.2.1+1)
public static <K, V> java.util.Map<K, V> ofLazy(java.util.Set<? extends K>, java.util.function.Function<? super K, ? extends V>);
$ javap java.util.Set | grep ofLazy (26.0.2.1+1)
$ javap java.util.List | grep ofLazy (27+35)
public static <E> java.util.List<E> ofLazy(int, java.util.function.IntFunction<? extends E>);
$ javap java.util.Map | grep ofLazy (27+35)
public static <K, V> java.util.Map<K, V> ofLazy(java.util.Set<? extends K>, java.util.function.Function<? super K, ? extends V>);
$ javap java.util.Set | grep ofLazy (27+35)
public static <E> java.util.Set<E> ofLazy(java.util.Set<? extends E>, java.util.function.Predicate<? super E>);
$ javac --enable-preview --release 25 src/StableValue25.java && java --enable-preview StableValue25 (25.0.4.1+1-LTS)
[load] configuration
CONFIG.get(): loaded
CONFIG.get(): loaded
[compute] orElseSet
lazy(): computed
lazy(): computed
[element] 2
LIST.get(2): v2
$ javac --enable-preview --release 26 broken/StillStable.java (26.0.2.1+1)
broken/StillStable.java:3: error: cannot find symbol
static final StableValue<String> CONFIG = StableValue.of();
^
symbol: class StableValue
location: class StillStable
broken/StillStable.java:3: error: cannot find symbol
static final StableValue<String> CONFIG = StableValue.of();
^
symbol: variable StableValue
location: class StillStable
2 errors
exit=1
$ javac --enable-preview --release 27 broken/StillStable.java (27+35)
broken/StillStable.java:3: error: cannot find symbol
static final StableValue<String> CONFIG = StableValue.of();
^
symbol: class StableValue
location: class StillStable
broken/StillStable.java:3: error: cannot find symbol
static final StableValue<String> CONFIG = StableValue.of();
^
symbol: variable StableValue
location: class StillStable
2 errors
exit=1
$ javac --enable-preview --release 26 broken/RemovedApi.java (26.0.2.1+1)
exit=0
$ javac --enable-preview --release 27 broken/RemovedApi.java (27+35)
broken/RemovedApi.java:4: error: cannot find symbol
System.out.println(c.isInitialized()); // exists on JDK 26, removed in 27
^
symbol: method isInitialized()
location: variable c of type LazyConstant<String>
broken/RemovedApi.java:5: error: cannot find symbol
System.out.println(c.orElse("fallback")); // exists on JDK 26, removed in 27
^
symbol: method orElse(String)
location: variable c of type LazyConstant<String>
2 errors
exit=1
$ javac --enable-preview --release 27 broken/ReassignInSupplier.java (27+35)
broken/ReassignInSupplier.java:5: error: cannot find symbol
c.set("y");
^
symbol: method set(String)
location: variable c of type LazyConstant<String>
1 error
exit=1
@@ -0,0 +1,23 @@
$ javac broken/NoPreviewFlag.java (27+35)
broken/NoPreviewFlag.java:3: error: LazyConstant is a preview API and is disabled by default.
static final LazyConstant<String> CONFIG = LazyConstant.of(() -> "x");
^
(use --enable-preview to enable preview APIs)
broken/NoPreviewFlag.java:3: error: LazyConstant is a preview API and is disabled by default.
static final LazyConstant<String> CONFIG = LazyConstant.of(() -> "x");
^
(use --enable-preview to enable preview APIs)
2 errors
exit=1
$ javap -v Basics | grep 'major\|minor' (27+35, compiled with --enable-preview --release 27)
minor version: 65535
major version: 71
$ java Basics (27+35, without --enable-preview)
Error: LinkageError occurred while loading main class Basics
java.lang.UnsupportedClassVersionError: Preview features are not enabled for Basics (class file version 71.65535). Try running with '--enable-preview'
$ java --enable-preview Basics (26.0.2.1+1, class file built by 27)
Error: LinkageError occurred while loading main class Basics
java.lang.UnsupportedClassVersionError: Basics has been compiled by a more recent version of the Java Runtime (class file version 71.65535), this version of the Java Runtime only recognizes class file versions up to 70.0
+9
View File
@@ -0,0 +1,9 @@
$ java --enable-preview org.openjdk.jmh.Main -f 3 -wi 3 -w 1 -i 5 -r 1 (27+35, JMH 1.37)
machine: 2 CPUs, Intel(R) Xeon(R) Processor @ 2.80GHz
Benchmark Mode Cnt Score Error Units
AccessBench.doubleCheckedLocking avgt 15 0.964 ± 0.028 ns/op
AccessBench.eagerStaticFinal avgt 15 0.648 ± 0.018 ns/op
AccessBench.holderIdiom avgt 15 0.639 ± 0.024 ns/op
AccessBench.lazyConstantInstanceField avgt 15 1.465 ± 0.065 ns/op
AccessBench.lazyConstantNonFinalStatic avgt 15 1.494 ± 0.064 ns/op
AccessBench.lazyConstantStaticFinal avgt 15 0.636 ± 0.019 ns/op