diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java
index 85f9d0e82fd..77d36bb2f52 100644
--- a/src/java.base/share/classes/java/lang/LazyConstant.java
+++ b/src/java.base/share/classes/java/lang/LazyConstant.java
@@ -29,28 +29,35 @@ import jdk.internal.javac.PreviewFeature;
import jdk.internal.lang.LazyConstantImpl;
import java.io.Serializable;
-import java.util.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
/**
- * A lazy constant is a holder of contents that can be set at most once.
+ * A lazy constant is a holder of content that can be initialized at most once.
*
* A lazy constant is created using the factory method
* {@linkplain LazyConstant#of(Supplier) LazyConstant.of({@code })}.
*
- * When created, the lazy constant is not initialized, meaning it has no contents.
+ * When created, the lazy constant is not initialized, meaning it has no content.
*
* The lazy constant (of type {@code T}) can then be initialized
- * (and its contents retrieved) by calling {@linkplain #get() get()}. The first time
+ * (and its content retrieved) by calling {@linkplain #get() get()}. The first time
* {@linkplain #get() get()} is called, the underlying computing function
* (provided at construction) will be invoked and the result will be used to initialize
* the constant.
*
- * Once a lazy constant is initialized, its contents can never change
- * and will be retrieved over and over again upon subsequent {@linkplain #get() get()}
- * invocations.
+ * Once a lazy constant is initialized, its content can never change
+ * and will always be returned by subsequent {@linkplain #get() get()} invocations.
*
* Consider the following example where a lazy constant field "{@code logger}" holds
* an object of type {@code Logger}:
@@ -83,12 +90,25 @@ import java.util.function.Supplier;
* may result in storage resources being prepared.
*
*
Exception handling
- * If the computing function returns {@code null}, a {@linkplain NullPointerException}
- * is thrown. Hence, a lazy constant can never hold a {@code null} value. Clients who
- * want to use a nullable constant can wrap the value into an {@linkplain Optional} holder.
+ * If evaluation of the computing function throws an unchecked exception (i.e., a runtime
+ * exception or an error), the lazy constant is not initialized but instead transitions to
+ * an error state whereafter a {@linkplain NoSuchElementException} is thrown with the
+ * unchecked exception as a cause. Subsequent {@linkplain #get() get()} calls throw
+ * {@linkplain NoSuchElementException} (without ever invoking the computing function
+ * again) with no cause and with a message that includes the name of the original
+ * unchecked exception's class.
*
- * If the computing function recursively invokes itself via the lazy constant, an
- * {@linkplain IllegalStateException} is thrown, and the lazy constant is not initialized.
+ * All failures are handled in this way. There are two special cases that cause unchecked
+ * exceptions to be thrown:
+ *
+ * If the computing function returns {@code null}, a {@linkplain NoSuchElementException}
+ * (with a {@linkplain NullPointerException} as a cause) will be thrown. Hence, a
+ * lazy constant can never hold a {@code null} value. Clients who want to use a nullable
+ * constant can wrap the value into an {@linkplain Optional} holder.
+ *
+ * If the computing function recursively invokes itself via the lazy constant, a
+ * {@linkplain NoSuchElementException} (with an {@linkplain IllegalStateException} as a
+ * cause) will be thrown.
*
*
Composing lazy constants
* A lazy constant can depend on other lazy constants, forming a dependency graph
@@ -98,31 +118,25 @@ import java.util.function.Supplier;
* which are held by lazy constants:
*
* {@snippet lang = java:
- * public final class DependencyUtil {
+ * public static class Foo {
+ * // ...
+ * }
*
- * private DependencyUtil() {}
- *
- * public static class Foo {
+ * public static class Bar {
+ * public Bar(Foo foo) {
* // ...
- * }
- *
- * public static class Bar {
- * public Bar(Foo foo) {
- * // ...
- * }
* }
+ * }
*
- * private static final LazyConstant FOO = LazyConstant.of( Foo::new );
- * private static final LazyConstant BAR = LazyConstant.of( () -> new Bar(FOO.get()) );
+ * static final LazyConstant FOO = LazyConstant.of( Foo::new );
+ * static final LazyConstant BAR = LazyConstant.of( () -> new Bar(FOO.get()) );
*
- * public static Foo foo() {
- * return FOO.get();
- * }
- *
- * public static Bar bar() {
- * return BAR.get();
- * }
+ * public static Foo foo() {
+ * return FOO.get();
+ * }
*
+ * public static Bar bar() {
+ * return BAR.get();
* }
* }
* Calling {@code BAR.get()} will create the {@code Bar} singleton if it is not already
@@ -134,13 +148,17 @@ import java.util.function.Supplier;
* competing threads are racing to initialize a lazy constant, only one updating thread
* runs the computing function (which runs on the caller's thread and is hereafter denoted
* the computing thread), while the other threads are blocked until the constant
- * is initialized, after which the other threads observe the lazy constant is initialized
- * and leave the constant unchanged and will never invoke any computation.
+ * is initialized (or computation fails), after which the other threads observe the lazy
+ * constant is initialized (or has transisioned to an error state) and leave the constant
+ * unchanged and will never invoke any computation.
*
* The invocation of the computing function and the resulting initialization of
* the constant {@linkplain java.util.concurrent##MemoryVisibility happens-before}
* the initialized constant's content is read. Hence, the initialized constant's content,
* including any {@code final} fields of any newly created objects, is safely published.
+ * As subsequent retrieval of the content might be elided, there are no other memory
+ * ordering or visibility guarantees provided as a consequence of calling
+ * {@linkplain #get()} again.
*
* Thread interruption does not cancel the initialization of a lazy constant. In other
* words, if the computing thread is interrupted, {@code LazyConstant::get} doesn't clear
@@ -150,9 +168,9 @@ import java.util.function.Supplier;
* lazy constant may block indefinitely; no timeouts or cancellations are provided.
*
*
Performance
- * The contents of a lazy constant can never change after the lazy constant has been
+ * The content of a lazy constant can never change after the lazy constant has been
* initialized. Therefore, a JVM implementation may, for an initialized lazy constant,
- * elide all future reads of that lazy constant's contents and instead use the contents
+ * elide all future reads of that lazy constant's content and instead use the content
* that has been previously observed. We call this optimization constant folding.
* This is only possible if there is a direct reference from a {@code static final} field
* to a lazy constant or if there is a chain from a {@code static final} field -- via one
@@ -160,15 +178,10 @@ import java.util.function.Supplier;
* {@linkplain Record record} fields, or final instance fields in hidden classes) --
* to a lazy constant.
*
- *
Miscellaneous
- * Except for {@linkplain Object#equals(Object) equals(obj)} and
- * {@linkplain #orElse(Object) orElse(other)} parameters, all method parameters
- * must be non-null, or a {@link NullPointerException} will be thrown.
- *
- * @apiNote Once a lazy constant is initialized, its contents cannot ever be removed.
+ * @apiNote Once a lazy constant is initialized, its content can't be removed.
* This can be a source of an unintended memory leak. More specifically,
* a lazy constant {@linkplain java.lang.ref##reachability strongly references}
- * it contents. Hence, the contents of a lazy constant will be reachable as long
+ * its content. Hence, the content of a lazy constant will be reachable as long
* as the lazy constant itself is reachable.
*
* While it's possible to store an array inside a lazy constant, doing so will
@@ -185,7 +198,7 @@ import java.util.function.Supplier;
* @implNote
* A lazy constant is free to synchronize on itself. Hence, care must be
* taken when directly or indirectly synchronizing on a lazy constant.
- * A lazy constant is unmodifiable but its contents may or may not be
+ * A lazy constant is unmodifiable but its content may or may not be
* immutable (e.g., it may hold an {@linkplain ArrayList}).
*
* @param type of the constant
@@ -205,39 +218,24 @@ public sealed interface LazyConstant
permits LazyConstantImpl {
/**
- * {@return the contents of this lazy constant if initialized, otherwise,
- * returns {@code other}}
+ * {@return the initialized content of this constant, computing it if necessary}
*
- * This method never triggers initialization of this lazy constant and will observe
- * initialization by other threads atomically (i.e., it returns the contents
- * if and only if the initialization has already completed).
- *
- * @param other value to return if the content is not initialized
- * (can be {@code null})
- */
- T orElse(T other);
-
- /**
- * {@return the contents of this initialized constant. If not initialized, first
- * computes and initializes this constant using the computing function}
+ * If this constant is not initialized, first computes and initializes it
+ * using the computing function.
*
* After this method returns successfully, the constant is guaranteed to be
* initialized.
*
- * If the computing function throws, the throwable is relayed to the caller and
- * the lazy constant remains uninitialized; a subsequent call to get() may then
- * attempt the computation again.
+ * If an unchecked exception is thrown when evaluating the computing function or if
+ * the computing function returns {@code null}, this lazy constant is not initialized
+ * but transitions to an error state whereafter a {@linkplain NoSuchElementException}
+ * is thrown as described in the {@linkplain ##exception-handling Exception handling}
+ * section.
+ *
+ * @throws NoSuchElementException if this lazy constant is in an error state
*/
T get();
- /**
- * {@return {@code true} if the constant is initialized, {@code false} otherwise}
- *
- * This method never triggers initialization of this lazy constant and will observe
- * changes in the initialization state made by other threads atomically.
- */
- boolean isInitialized();
-
// Object methods
/**
@@ -245,7 +243,7 @@ public sealed interface LazyConstant
* the provided {@code obj}, otherwise {@code false}}
*
* In other words, equals compares the identity of this lazy constant and {@code obj}
- * to determine equality. Hence, two distinct lazy constants with the same contents are
+ * to determine equality. Hence, two distinct lazy constants with the same content are
* not equal.
*
* This method never triggers initialization of this lazy constant.
@@ -267,11 +265,11 @@ public sealed interface LazyConstant
*
* This method never triggers initialization of this lazy constant and will observe
* initialization by other threads atomically (i.e., it observes the
- * contents if and only if the initialization has already completed).
+ * content if and only if the initialization has already completed).
*
* If this lazy constant is initialized, an implementation-dependent string
* containing the {@linkplain Object#toString()} of the
- * contents will be returned; otherwise, an implementation-dependent string is
+ * content will be returned; otherwise, an implementation-dependent string is
* returned that indicates this lazy constant is not yet initialized.
*/
@Override
@@ -280,31 +278,41 @@ public sealed interface LazyConstant
// Factory
/**
- * {@return a lazy constant whose contents is to be computed later via the provided
- * {@code computingFunction}}
+ * {@return a new lazy constant whose content is to be computed later via the
+ * provided {@code computingFunction}}
*
* The returned lazy constant strongly references the provided
- * {@code computingFunction} at least until initialization completes successfully.
+ * {@code computingFunction} until computation completes (successfully or with
+ * failure).
*
- * If the provided computing function is already an instance of
- * {@code LazyConstant}, the method is free to return the provided computing function
- * directly.
+ * By design, the method always returns a new lazy constant even if the provided
+ * computing function is already an instance of {@code LazyConstant}. Clients that
+ * want to elide creation under this condition can write a utility method similar
+ * to the one in the snippet below and create lazy constants via this method rather
+ * than calling the built-in factory {@linkplain #of(Supplier)} directly:
*
- * @implNote after initialization completes successfully, the computing function is
- * no longer strongly referenced and becomes eligible for
- * garbage collection.
+ * {@snippet lang = java:
+ * static LazyConstant ofFlattened(Supplier extends T> computingFunction) {
+ * return (computingFunction instanceof LazyConstant extends T> lc)
+ * ? (LazyConstant) lc // unchecked cast is safe under normal generic usage
+ * : LazyConstant.of(computingFunction);
+ * }
+ * }
+ *
+ * @implNote after the computing function completes (regardless of whether it
+ * succeeds or throws an unchecked exception), the computing function is no
+ * longer strongly referenced and becomes eligible for garbage collection.
*
* @param computingFunction in the form of a {@linkplain Supplier} to be used
* to initialize the constant
* @param type of the constant
+ * @throws NullPointerException if the provided {@code computingFunction} is
+ * {@code null}
*
*/
@SuppressWarnings("unchecked")
static LazyConstant of(Supplier extends T> computingFunction) {
Objects.requireNonNull(computingFunction);
- if (computingFunction instanceof LazyConstant extends T> lc) {
- return (LazyConstant) lc;
- }
return LazyConstantImpl.ofLazy(computingFunction);
}
diff --git a/src/java.base/share/classes/java/util/LazyCollections.java b/src/java.base/share/classes/java/util/LazyCollections.java
index 0bbdad87ac4..e15721db2ce 100644
--- a/src/java.base/share/classes/java/util/LazyCollections.java
+++ b/src/java.base/share/classes/java/util/LazyCollections.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,20 +25,21 @@
package java.util;
+import jdk.internal.lang.LazyConstantImpl;
import jdk.internal.misc.Unsafe;
import jdk.internal.util.ImmutableBitSetPredicate;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
-import java.lang.LazyConstant;
import java.lang.reflect.Array;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
-import java.util.function.Supplier;
+import java.util.function.Predicate;
/**
* Container class for lazy collections implementations. Not part of the public API.
@@ -54,6 +55,7 @@ final class LazyCollections {
// Unsafe allows LazyCollection classes to be used early in the boot sequence
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
+ @jdk.internal.vm.annotation.TrustFinalFields
@jdk.internal.ValueBased
static final class LazyList
extends ImmutableCollections.AbstractImmutableList {
@@ -62,18 +64,17 @@ final class LazyCollections {
private final E[] elements;
// Keeping track of `size` separately reduces bytecode size compared to
// using `elements.length`.
- @Stable
private final int size;
- @Stable
- final FunctionHolder> functionHolder;
- @Stable
+ private final FunctionHolder> functionHolder;
private final Mutexes mutexes;
+ private final Throwables throwables;
private LazyList(int size, IntFunction extends E> computingFunction) {
this.elements = newGenericArray(size);
this.size = size;
this.functionHolder = new FunctionHolder<>(computingFunction, size);
this.mutexes = new Mutexes(size);
+ this.throwables = new Throwables(size);
super();
}
@@ -89,7 +90,7 @@ final class LazyCollections {
}
private E getSlowPath(int i) {
- return orElseComputeSlowPath(elements, i, mutexes, i, functionHolder);
+ return orElseComputeSlowPath(elements, i, mutexes, throwables, i, functionHolder);
}
@Override
@@ -109,6 +110,7 @@ final class LazyCollections {
@Override
public int indexOf(Object o) {
+ Objects.requireNonNull(o);
for (int i = 0; i < size; i++) {
if (Objects.equals(o, get(i))) {
return i;
@@ -119,6 +121,7 @@ final class LazyCollections {
@Override
public int lastIndexOf(Object o) {
+ Objects.requireNonNull(o);
for (int i = size - 1; i >= 0; i--) {
if (Objects.equals(o, get(i))) {
return i;
@@ -143,16 +146,14 @@ final class LazyCollections {
}
- static final class LazyEnumMap, V>
+ @jdk.internal.vm.annotation.TrustFinalFields
+ private static final class LazyEnumMap, V>
extends AbstractLazyMap {
- @Stable
private final Class enumType;
- @Stable
// We are using a wrapper class here to be able to use a min value of zero that
// is also stable.
private final Integer min;
- @Stable
private final IntPredicate member;
public LazyEnumMap(Set set,
@@ -182,29 +183,26 @@ final class LazyCollections {
if (member.test(ordinal)) {
@SuppressWarnings("unchecked")
final K k = (K) key;
- return orElseCompute(k, indexForAsInt(k));
+ return orElseCompute(k, indexFor(k));
}
}
return defaultValue;
}
+ @ForceInline
@Override
- Integer indexFor(K key) {
- return indexForAsInt(key);
- }
-
- private int indexForAsInt(K key) {
+ int indexFor(K key) {
return key.ordinal() - min;
}
}
- static final class LazyMap
+ @jdk.internal.vm.annotation.TrustFinalFields
+ private static final class LazyMap
extends AbstractLazyMap {
// Use an unmodifiable map with known entries that are @Stable. Lookups through this map can be folded because
- // it is created using Map.ofEntrie. This allows us to avoid creating a separate hashing function.
- @Stable
+ // it is created using Map.ofEntries. This allows us to avoid creating a separate hashing function.
private final Map indexMapper;
public LazyMap(Set keys, Function super K, ? extends V> computingFunction) {
@@ -232,29 +230,34 @@ final class LazyCollections {
@Override public boolean containsKey(Object o) { return indexMapper.containsKey(o); }
+ @ForceInline
@Override
- Integer indexFor(K key) {
+ // This method will throw an NPE if the key does not exist. So, callers need to
+ // make sure the key exist before invoking this method.
+ int indexFor(K key) {
return indexMapper.get(key);
}
}
- static sealed abstract class AbstractLazyMap
+ @jdk.internal.vm.annotation.TrustFinalFields
+ private static abstract sealed class AbstractLazyMap
extends ImmutableCollections.AbstractImmutableMap {
- // This field shadows AbstractMap.keySet which is not @Stable.
- @Stable
- Set keySet;
+ private final Mutexes mutexes;
+ private final Throwables throwables;
+ private final int size;
+ private final FunctionHolder> functionHolder;
+ private final Set> entrySet;
// This field shadows AbstractMap.values which is of another type
@Stable
- final V[] values;
+ private final V[] values;
+ // This field shadows AbstractMap.keySet which is not trusted
+ private final Set keySet;
+
+ // We are using a `long` here to get stable access even in the case
+ // that the 32-bit hash code is zero.
@Stable
- Mutexes mutexes;
- @Stable
- private final int size;
- @Stable
- final FunctionHolder> functionHolder;
- @Stable
- private final Set> entrySet;
+ private long hash;
private AbstractLazyMap(Set keySet,
int size,
@@ -264,14 +267,15 @@ final class LazyCollections {
this.functionHolder = new FunctionHolder<>(computingFunction, size);
this.values = newGenericArray(backingSize);
this.mutexes = new Mutexes(backingSize);
- super();
+ this.throwables = new Throwables(backingSize);
this.keySet = keySet;
+ super();
this.entrySet = LazyMapEntrySet.of(this);
}
// Abstract methods
@Override public abstract boolean containsKey(Object o);
- abstract Integer indexFor(K key);
+ abstract int indexFor(K key);
// Public methods
@Override public final int size() { return size; }
@@ -279,6 +283,45 @@ final class LazyCollections {
@Override public final Set> entrySet() { return entrySet; }
@Override public Set keySet() { return keySet; }
+ @Override
+ public final boolean containsValue(Object value) {
+ Objects.requireNonNull(value);
+ for (K key : keySet) {
+ if (value.equals(orElseCompute(key, indexFor(key)))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ // Racy computation
+ long h = hash;
+ if (h == 0) {
+ // Set a bit in the upper 32-bit region of the `long` to
+ // cater for the case the lower 32-bit hash is zero.
+ hash = h = expandToLong(hashCode0());
+ }
+ return reduceToInt(h);
+ }
+
+ private int hashCode0() {
+ int hash = 0;
+ for (K key : keySet) {
+ hash += key.hashCode() ^ orElseCompute(key, indexFor(key)).hashCode();
+ }
+ return hash;
+ }
+
+ @Override
+ public final void forEach(BiConsumer super K, ? super V> action) {
+ Objects.requireNonNull(action);
+ for (K key : keySet) {
+ action.accept(key, orElseCompute(key, indexFor(key)));
+ }
+ }
+
@ForceInline
@Override
public final V get(Object key) {
@@ -293,15 +336,15 @@ final class LazyCollections {
if (v != null) {
return v;
}
- return orElseComputeSlowPath(values, index, mutexes, key, functionHolder);
+ return orElseComputeSlowPath(values, index, mutexes, throwables, key, functionHolder);
}
+ @jdk.internal.vm.annotation.TrustFinalFields
@jdk.internal.ValueBased
- static final class LazyMapEntrySet extends ImmutableCollections.AbstractImmutableSet> {
+ private static final class LazyMapEntrySet extends ImmutableCollections.AbstractImmutableSet> {
// Use a separate field for the outer class in order to facilitate
- // a @Stable annotation.
- @Stable
+ // a trusted field.
private final AbstractLazyMap map;
private LazyMapEntrySet(AbstractLazyMap map) {
@@ -318,14 +361,13 @@ final class LazyCollections {
return new LazyMapEntrySet<>(outer);
}
+ @jdk.internal.vm.annotation.TrustFinalFields
@jdk.internal.ValueBased
static final class LazyMapIterator implements Iterator> {
// Use a separate field for the outer class in order to facilitate
- // a @Stable annotation.
- @Stable
+ // a trusted field.
private final AbstractLazyMap map;
- @Stable
private final Iterator keyIterator;
private LazyMapIterator(AbstractLazyMap map) {
@@ -334,21 +376,22 @@ final class LazyCollections {
super();
}
- @Override public boolean hasNext() { return keyIterator.hasNext(); }
+ @Override public boolean hasNext() { return keyIterator.hasNext(); }
@Override
public Entry next() {
final K k = keyIterator.next();
- return new LazyEntry<>(k, map, map.functionHolder);
+ return new LazyEntry<>(k, map);
}
@Override
public void forEachRemaining(Consumer super Entry> action) {
+ Objects.requireNonNull(action);
final Consumer super K> innerAction =
new Consumer<>() {
@Override
public void accept(K key) {
- action.accept(new LazyEntry<>(key, map, map.functionHolder));
+ action.accept(new LazyEntry<>(key, map));
}
};
keyIterator.forEachRemaining(innerAction);
@@ -362,13 +405,12 @@ final class LazyCollections {
}
}
- private record LazyEntry(K getKey, // trick
- AbstractLazyMap map,
- FunctionHolder> functionHolder) implements Entry {
+ private record LazyEntry(@Override K getKey, // trick
+ AbstractLazyMap map) implements Entry {
@Override public V setValue(V value) { throw ImmutableCollections.uoe(); }
@Override public V getValue() { return map.orElseCompute(getKey, map.indexFor(getKey)); }
- @Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); }
+ @Override public int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); }
@Override public String toString() { return getKey() + "=" + getValue(); }
@Override
@@ -379,9 +421,6 @@ final class LazyCollections {
&& Objects.equals(getValue(), e.getValue());
}
- private int hash(Object obj) {
- return (obj == null) ? 0 : obj.hashCode();
- }
}
@Override
@@ -389,12 +428,12 @@ final class LazyCollections {
return LazyMapValues.of(this);
}
+ @jdk.internal.vm.annotation.TrustFinalFields
@jdk.internal.ValueBased
static final class LazyMapValues extends ImmutableCollections.AbstractImmutableCollection {
// Use a separate field for the outer class in order to facilitate
- // a @Stable annotation.
- @Stable
+ // a trusted field.
private final AbstractLazyMap map;
private LazyMapValues(AbstractLazyMap map) {
@@ -416,7 +455,133 @@ final class LazyCollections {
}
- static final class Mutexes {
+ @jdk.internal.vm.annotation.TrustFinalFields
+ private static final class LazySet
+ extends ImmutableCollections.AbstractImmutableSet
+ implements Set {
+
+ private final Map map;
+
+ // -1 is used as a sentinel value for zero so we can get
+ // stable access for all `size` values. `size` is always non-negative.
+ @Stable
+ private int size;
+ // We are using a `long` here to get stable access even in the case
+ // that the 32-bit hash code is zero.
+ @Stable
+ private long hash;
+
+ public LazySet(Set extends E> elementCandidates,
+ Predicate super E> computingFunction) {
+ this.map = Map.ofLazy(elementCandidates, computingFunction::test);
+ super();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return map.getOrDefault(o, Boolean.FALSE).booleanValue();
+ }
+
+ @Override
+ public int hashCode() {
+ // Racy computation
+ long h = hash;
+ if (h == 0) {
+ // Set a bit in the upper 32-bit region of the `long` to
+ // cater for the case the lower 32-bit hash is zero.
+ hash = h = expandToLong(hashCode0());
+ }
+ return reduceToInt(h);
+ }
+
+ private int hashCode0() {
+ int hash = 0;
+ for (var e: map.entrySet()) {
+ if (e.getValue()) {
+ hash += e.getKey().hashCode();
+ }
+ }
+ return hash;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return new LazySetIterator<>(map.entrySet().iterator());
+ }
+
+ @jdk.internal.vm.annotation.TrustFinalFields
+ static final class LazySetIterator implements Iterator {
+
+ private final Iterator> iterator;
+
+ E current;
+
+ public LazySetIterator(Iterator> iterator) {
+ this.iterator = iterator;
+ super();
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (current != null) {
+ return true;
+ }
+ while (iterator.hasNext()) {
+ Map.Entry e = iterator.next();
+ if (e.getValue()) {
+ current = e.getKey();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public E next() {
+ E e = current;
+ if (e != null) {
+ return consumeCurrent(e);
+ }
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ return consumeCurrent(current);
+ }
+
+ private E consumeCurrent(E e) {
+ current = null;
+ return e;
+ }
+
+ }
+
+ @Override
+ public int size() {
+ // Racy computation
+ int s = size;
+ if (s == 0) {
+ s = size0();
+ if (s == 0) {
+ s = -1;
+ }
+ size = s;
+ }
+ return s == -1 ? 0 : s;
+ }
+
+ private int size0() {
+ int size = 0;
+ for (var e: map.entrySet()) {
+ if (e.getValue()) {
+ size++;
+ }
+ }
+ return size;
+ }
+
+ }
+
+ private static final class Mutexes {
private static final Object TOMB_STONE = new Object();
@@ -431,9 +596,15 @@ final class LazyCollections {
this.counter = new AtomicInteger(length);
}
- @ForceInline
private Object acquireMutex(long offset) {
- assert mutexes != null;
+ // Snapshot
+ var mutexes = this.mutexes;
+ if (mutexes == null) {
+ // We have already computed all the elements and if we end up here
+ // there was at least one unchecked exception thrown by the
+ // computing function.
+ return null;
+ }
// Check if there already is a mutex (Object or TOMB_STONE)
final Object mutex = UNSAFE.getReferenceVolatile(mutexes, offset);
if (mutex != null) {
@@ -447,7 +618,7 @@ final class LazyCollections {
private void releaseMutex(long offset) {
// Replace the old mutex with a tomb stone since now the old mutex can be collected.
- UNSAFE.putReference(mutexes, offset, TOMB_STONE);
+ UNSAFE.putReferenceVolatile(mutexes, offset, TOMB_STONE);
if (counter != null && counter.decrementAndGet() == 0) {
mutexes = null;
counter = null;
@@ -456,6 +627,33 @@ final class LazyCollections {
}
+ /** Holds the throwable class names produced by the computing function.
+ *
+ * Class names are used instead of Class objects to avoid pinning class loaders after
+ * a failed computation.
+ *
+ * This class is not thread safe across indices. However, it will always be accessed
+ * under the same monitor for a given index.
+ */
+ private static final class Throwables {
+
+ @Stable
+ final String[] throwables;
+
+ Throwables(int size) {
+ this.throwables = new String[size];
+ super();
+ }
+
+ Optional get(int index) {
+ return Optional.ofNullable(throwables[index]);
+ }
+
+ void set(int index, Throwable throwable) {
+ throwables[index] = throwable.getClass().getName().intern();
+ }
+ }
+
@ForceInline
private static long offsetFor(long index) {
return Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * index;
@@ -466,75 +664,81 @@ final class LazyCollections {
return (E[]) new Object[length];
}
- public static List ofLazyList(int size,
- IntFunction extends E> computingFunction) {
- return new LazyList<>(size, computingFunction);
- }
-
- public static Map ofLazyMap(Set keys,
- Function super K, ? extends V> computingFunction) {
- return new LazyMap<>(keys, computingFunction);
- }
@SuppressWarnings("unchecked")
- public static , V>
- Map ofLazyMapWithEnumKeys(Set keys,
- Function super K, ? extends V> computingFunction) {
- // The input set is not empty
- final Class enumType = ((E) keys.iterator().next()).getDeclaringClass();
- final BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
- int min = Integer.MAX_VALUE;
- int max = Integer.MIN_VALUE;
- for (K t : keys) {
- final int ordinal = ((E) t).ordinal();
- min = Math.min(min, ordinal);
- max = Math.max(max, ordinal);
- bitSet.set(ordinal);
- }
- final int backingSize = max - min + 1;
- final IntPredicate member = ImmutableBitSetPredicate.of(bitSet);
- return (Map) new LazyEnumMap<>((Set) keys, enumType, min, backingSize, member, (Function) computingFunction);
- }
-
- @SuppressWarnings("unchecked")
- static T orElseComputeSlowPath(final T[] array,
+ private static T orElseComputeSlowPath(final T[] array,
final int index,
final Mutexes mutexes,
+ final Throwables throwables,
final Object input,
final FunctionHolder> functionHolder) {
final long offset = offsetFor(index);
final Object mutex = mutexes.acquireMutex(offset);
- preventReentry(mutex);
+ if (mutex == null) {
+ throwIfPreviousException(index, throwables, input);
+ // There must be an exception
+ throw cannotReachHere(functionHolder, input);
+ }
+ preventReentry(mutex, input);
synchronized (mutex) {
final T t = array[index]; // Plain semantics suffice here
if (t == null) {
- final T newValue = switch (functionHolder.function()) {
- case IntFunction> iFun -> (T) iFun.apply((int) input);
- case Function, ?> fun -> ((Function