On Thu, 2 Oct 2025 10:51:08 GMT, Per Minborg <[email protected]> wrote:
> Implement JEP 526: Lazy Constants (Second Preview) > > The lazy list/map implementations are broken out from `ImmutableCollections` > to a separate class. > > The old benchmarks are not moved/renamed to allow comparison with previous > releases. > > `java.util.Optional` is updated so that its field is annotated with > `@Stable`. This is to allow `Optional` instances to be held in lazy > constants and still provide constant folding. I’m gonna miss **Stable Values**, as it has some use cases which aren’t served by **Lazy Constants**, and on which I depend on in some of my code, so I’m stuck with using regular non‑`final` fields. -------------------------------------------------------------------------------- Also, in the [JEP 526] table under “[Flexible initialization with lazy constants]”: > | | Update count | Update location | Constant > folding? | Concurrent updates? > | -------------- | ------------ | --------------- | > ----------------- | ------------------- > | `final` field | 1 | Constructor or static initializer > | Yes | No > | `LazyConstant` | [0, 1] | Anywhere | Yes, after > initialization | Yes, by winner > | Non-`final` field | [0, ∞) | Anywhere | No | Yes The “Update location” of `LazyConstant` shouldn’t be “Anywhere”, as that was only accurate for `StableValue`, but not for `LazyConstant`, which is updated by calling the passed `Supplier`. Similarly, concurrent updates are prevented for `LazyConstant`s by using `synchronized (this)`. [JEP 526]: https://openjdk.org/jeps/526 [Flexible initialization with lazy constants]: https://openjdk.org/jeps/526#Flexible-initialization-with-lazy-constants Getting access to the underlying `StableValue` API with **Lazy Constants** is way too hacky and convoluted (but doable): <details> <summary>StableVar.java</summary> /* * Any copyright is dedicated to the Public Domain. * https://creativecommons.org/publicdomain/zero/1.0/ */ import java.util.NoSuchElementException; import java.util.function.Supplier; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import static java.lang.System.identityHashCode; import static java.util.Objects.requireNonNull; /// Horrible awful hack to get access to raw stable values in JDK 26+. @NullMarked public sealed interface StableVar<T> permits StableHacks.StableVarImpl { boolean trySet(final T contents) throws NullPointerException, IllegalStateException; @Nullable T orNull(); T orElse(final T other) throws NullPointerException; T orElseThrow() throws NoSuchElementException; boolean isSet(); T orElseSet(final Supplier<? extends T> supplier) throws NullPointerException, IllegalStateException; void setOrThrow(final T contents) throws NullPointerException, IllegalStateException; static <T> StableVar<T> of() { return StableHacks.newInstance(); } } /// Encapsulates the actual implementation of `StableValue` on `LazyConstant` /// /// @author ExE Boss @NullMarked /*package*/ final @Namespace class StableHacks { private StableHacks() throws InstantiationException { throw new InstantiationException(StableHacks.class.getName()); } private static final String UNSET_SUFFIX = ".unset"; private static final Object UNSET = new Object() { @Override public int hashCode() { return 0; } @Override public String toString() { return "unset"; } }; private static final ScopedValue<?> SCOPE = ScopedValue.newInstance(); private static final Supplier<?> SCOPE_GETTER = SCOPE::get; /*package*/ static final <T> StableVarImpl<T> newInstance() { return new StableValue<>(); } /*package*/ sealed interface StableVarImpl<T> extends StableVar<T> { } private record StableValue<T>( // Implemented as a record so that the JVM treats this as a trusted final field // even when `-XX:+TrustFinalNonStaticFields` is not set LazyConstant<T> contents ) implements StableVarImpl<T> { @SuppressWarnings("unchecked") private StableValue() { this(LazyConstant.<T>of((Supplier) SCOPE_GETTER)); } private StableValue { if (contents.isInitialized()) throw new InternalError(); } @SuppressWarnings("unchecked") private final ScopedValue<T> scope() { return (ScopedValue<T>) SCOPE; } private final void preventReentry() throws IllegalStateException { if (Thread.holdsLock(this)) { throw new IllegalStateException("Recursive initialization of a stable value is illegal"); } } @Override public boolean trySet(final T contents) throws NullPointerException, IllegalStateException { requireNonNull(contents); if (this.contents.isInitialized()) return false; preventReentry(); synchronized (this) { return this.setImpl(contents); } } @Override @SuppressWarnings("unchecked") public final @Nullable T orNull() { return unwrapUnset(((LazyConstant) this.contents).orElse(UNSET)); } @Override public T orElse(T other) throws NullPointerException { return this.contents.orElse(other); } @Override public T orElseThrow() throws NoSuchElementException { { final T contents; if ((contents = this.orNull()) != null) { return contents; } } throw new NoSuchElementException(); } @Override public boolean isSet() { return this.contents.isInitialized(); } @Override public T orElseSet(final Supplier<? extends T> supplier) throws NullPointerException, IllegalStateException { requireNonNull(supplier); { final T contents; if ((contents = this.orNull()) != null) { return contents; } } return orElseSetSlowPath(supplier); } @Override public void setOrThrow(final T contents) throws NullPointerException, IllegalStateException { if (!trySet(contents)) { throw new IllegalStateException(); } } private final T orElseSetSlowPath( final Supplier<? extends T> supplier ) throws NullPointerException, IllegalStateException { preventReentry(); synchronized (this) { { final T contents; if ((contents = this.orNull()) != null) { return contents; } } final T newValue; this.setImpl(newValue = requireNonNull(supplier.get())); return newValue; } } private final boolean setImpl(final T contents) { assert Thread.holdsLock(this); if (this.contents.isInitialized()) { return false; } ScopedValue.where(this.scope(), contents).run(this.contents::get); return true; } @Override public final boolean equals(final Object obj) { return this == obj; } @Override public final int hashCode() { return identityHashCode(this); } @Override public String toString() { final Object contents; return renderValue( "StableValue", (contents = this.orNull()) != this ? contents : "(this StableValue)" ); } } @SuppressWarnings("unchecked") private static final <T> @Nullable T unwrapUnset(final @Nullable Object obj) { return (obj == UNSET) ? null : (T) obj; } private static final String renderValue( final String type, final @Nullable Object value ) throws NullPointerException { return (value == null) ? type.concat(UNSET_SUFFIX) : (type + '[' + value + ']'); } } </details> ------------- PR Comment: https://git.openjdk.org/jdk/pull/27605#issuecomment-3367784825 PR Comment: https://git.openjdk.org/jdk/pull/27605#issuecomment-3368298279
