bye stable values, enter lazy constants
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Mon Sep 29 17:07:25 UTC 2025
Hi Sergey, thanks for the feedback
On 29/09/2025 17:28, Sergey Bylokhov wrote:
> Hello Java Guru!
>
> There is a very small and mostly cosmetic problem with the code
> examples in https://openjdk.org/jeps/526 . The modifiers
> "private/public final" are used inconsistently. In some cases they are
> omitted, in others they are included. If they are added everywhere,
> some examples become unnecessarily verbose. For instance:
>
> private static final LazyConstant<OrderController> ORDERS =
> LazyConstant.of(OrderController::new)
ok, we'll see if we can make this more consistent
>
>
> It would be better if the new API could enforce these semantics
> regardless of whether the user adds final. In other words, laziness
> and stability should be intrinsic properties of the construct. Ideally
> something like:
>
> private static lazy OrderController ORDERS = OrderController::new
I'd like to keep this focussed on an API discussion, rather than trying
to compare the propsed API with "what would a language feature for this
look like".
If this is an API, then how can we ensure constant folding in the
absence of finality? The JVM has no idea that the LazyConstant object it
sees on a particular read will also be the same LazyConstant object the
next time around?
>
> Since introducing new keywords is not currently desirable, a practical
> alternative might be:
>
> private static final Lazy<OrderController> ORDERS =
> OrderController::new
>
> or
>
> private static final Lazy<OrderController> ORDERS = () -> new
> OrderController(...params...)
>
> In this case the Lazy class provides laziness and stability, while
> final ensures the reference itself is constant and therefore
> optimizable by the JVM.
So, your question is: since you need "final" anyway, why also needing
"Constant" in the name?
I think "final" and "Constant" mean two different things, really.
"final" just means (as it always did) that the field (an instance of
LazyConstant) cannot be reassigned. This is a necessary condition for
constant folding to occur.
But you also need another piece -- a promise that the value associated
with a LazyConstant changes _at most once_. That's why "Lazy", alone,
doesn't seem sufficient. "Lazy" just means initialized before use -- can
you also reassign the content? Maybe?
LazyAtMostOnce would have been more precise, but I don't think it's a
better name :-)
>
> At this point one may ask why a lambda or method reference must be
> wrapped in Lazy<T> at all. We already have a well-established
> abstraction for this: Supplier<T> and any other functional interfaces.
> Perhaps all we need is a way to mark such suppliers as stable? For
> example:
>
> private static final Supplier<OrderController> ORDERS = (() -> new
> OrderController(...)).stable();
Not sure about this one.
Your code doesn't work as is -- you need at least a cast:
((Supplier<OrderController>)() -> new OrderController(...)).stable();
This is not more readable than the proposed factory.
Also, if the type of the field is "just" a Supplier, other things might
be lost as well -- for instance, I believe the current prototype
automatically trusts all fields of type LazyConstant to be "trusted
final fields". This is not possible if the type of the field is just a
Supplier. I can also imagine that Leyden condensers might be more
interested in seeing a field with a LazyConstant type than just a
Supplier that might come from anywhere. But, of course, we've been
navigatig a similar space -- in the StableValue API we had the concept
of "caching supplier":
private static final Supplier<OrderController> ORDERS =
StableValue.ofSupplier(() -> new OrderController(...));
We could have put the factory inside Supplier (e.g.
Supplier::ofCaching), but when we started thinking about doing just
that, we realized that the resulting API would be less discoverable.
Of course, in a minimalistic world, we don't need a LazyConstant type,
we just add lazy suppliers, lists and maps. But we think that capturing
what a lazy constant is, once and for all, adds value -- maybe not
necessarily in terms of code you can write, but certainly in terms of
readability and clearly documenting programmers intent.
>
>
> The JEP is very performance and stability focused, but one important
> use case it tries to simplify is the initialization of singletons. A
> well-known current idiom described in the JEP is:
>
> public static Logger getLogger() {
> class Holder {
> private static final Logger LOGGER = Logger.create(...);
> }
> return Holder.LOGGER;
> }
>
> One great and useful property of this idiom is that the enclosing
> class's namespace is not polluted with unnecessary fields.
>
> By contrast, the new API encourages declaring fields directly at class
> scope. This introduces potential problems. A field may be initialized
> and accessed directly, bypassing the accessor method:
>
> private final LazyConstant<Logger> logger = LazyConstant.of(() ->
> Logger.create(OrderController.class))
>
> Or a field may remain unset until the accessor is executed, which
> could lead to exceptions if it is accessed elsewhere prematurely:
>
> private final LazyConstant<Logger> logger = LazyConstant.of()
True, a lazy constant field is, 99.99% of the time an implementation
detail. It is up to the implementor to make sure the lazy constant is
exposed correctly through public APIs.
While I understand your point re. encapsulation, cretaing a _whole new
class_ to avoid a class-level field, doesn't seem a great trade off.
I agree that a language-level keyword might push things further in this
space -- but that's not part of the JEP under discussion.
Cheers
Maurizio
More information about the leyden-dev
mailing list