Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
95 lines
4.6 KiB
Java
95 lines
4.6 KiB
Java
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
/**
|
|
* JEP 531, Lazy Constants (third preview), against the JDK 27 API.
|
|
*
|
|
* <p>A lazy constant is a value that is computed the first time somebody asks for it and never again,
|
|
* even when many threads ask at once. The JVM can then treat it like a {@code final} field. What changed
|
|
* since JDK 26: {@code orElse(...)} and {@code isInitialized()} are gone (see broken/Lazy26.java), and
|
|
* {@code Set.ofLazy(...)} arrived next to {@code List.ofLazy} and {@code Map.ofLazy}.
|
|
* Explained in docs/06-lazy-constants.md. Compile and run with --enable-preview.
|
|
*/
|
|
public class LazyDemo {
|
|
|
|
static final AtomicInteger INITIALISATIONS = new AtomicInteger();
|
|
|
|
/** The expensive thing: pretend this reads a file and parses it. */
|
|
record Settings(String region, int poolSize) {}
|
|
|
|
static Settings loadSettings() {
|
|
INITIALISATIONS.incrementAndGet();
|
|
System.out.println(" (loading settings on " + Thread.currentThread().getName() + ")");
|
|
return new Settings("ap-south-1", 16);
|
|
}
|
|
|
|
static final LazyConstant<Settings> SETTINGS = LazyConstant.of(LazyDemo::loadSettings);
|
|
|
|
/** Identity hashes and lambda addresses change on every run; blank them so the transcript is stable. */
|
|
static String tidy(Object o) {
|
|
return o.toString().replaceAll("@[0-9a-f]+", "@...").replaceAll("/0x[0-9a-f]+", "/0x...");
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
System.out.println("--- a lazy constant computes once, on first use");
|
|
System.out.println("toString before first get(): " + tidy(SETTINGS));
|
|
Settings first = SETTINGS.get();
|
|
Settings second = SETTINGS.get();
|
|
System.out.println("toString after first get(): " + tidy(SETTINGS));
|
|
Check.that(first == second, "get() returns the same instance every time");
|
|
Check.that(INITIALISATIONS.get() == 1, "the supplier ran exactly once");
|
|
|
|
System.out.println("--- and once even when 64 threads race for it");
|
|
var raced = LazyConstant.of(() -> {
|
|
INITIALISATIONS.incrementAndGet();
|
|
return "computed";
|
|
});
|
|
int before = INITIALISATIONS.get();
|
|
var start = new CountDownLatch(1);
|
|
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
|
|
for (int i = 0; i < 64; i++) {
|
|
pool.submit(() -> {
|
|
start.await();
|
|
return raced.get();
|
|
});
|
|
}
|
|
start.countDown();
|
|
}
|
|
Check.that(INITIALISATIONS.get() - before == 1, "64 racing threads triggered exactly one initialisation");
|
|
|
|
System.out.println("--- List.ofLazy: each element is computed on first access");
|
|
AtomicInteger listComputations = new AtomicInteger();
|
|
List<String> squares = List.ofLazy(5, i -> {
|
|
listComputations.incrementAndGet();
|
|
return "square(" + i + ")=" + (i * i);
|
|
});
|
|
System.out.println("computed so far: " + listComputations.get());
|
|
System.out.println("squares.get(3) = " + squares.get(3));
|
|
System.out.println("squares.get(3) = " + squares.get(3) + " (second read)");
|
|
Check.that(listComputations.get() == 1, "only element 3 was ever computed");
|
|
|
|
System.out.println("--- Map.ofLazy: the keys are fixed, the values are computed on first access");
|
|
AtomicInteger mapComputations = new AtomicInteger();
|
|
Map<String, Integer> lengths = Map.ofLazy(Set.of("alpha", "beta", "gamma"), k -> {
|
|
mapComputations.incrementAndGet();
|
|
return k.length();
|
|
});
|
|
System.out.println("lengths.get(\"gamma\") = " + lengths.get("gamma"));
|
|
Check.that(mapComputations.get() == 1, "only the requested key's value was computed");
|
|
|
|
System.out.println("--- Set.ofLazy (new in 27): membership is decided by a predicate, on demand");
|
|
AtomicInteger setComputations = new AtomicInteger();
|
|
Set<Integer> primes = Set.ofLazy(Set.of(2, 3, 4, 5, 6, 7), n -> {
|
|
setComputations.incrementAndGet();
|
|
return n > 1 && java.util.stream.IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(d -> n % d == 0);
|
|
});
|
|
System.out.println("primes.contains(7) = " + primes.contains(7) + ", primes.contains(6) = " + primes.contains(6));
|
|
Check.that(primes.contains(7) && !primes.contains(6), "the lazy set answers membership using the predicate");
|
|
System.out.println("primes = " + new java.util.TreeSet<>(primes));
|
|
}
|
|
}
|