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

Reply via email to