bye stable values, enter lazy constants
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Mon Sep 29 21:08:03 UTC 2025
On 29/09/2025 21:39, Sergey Bylokhov wrote:
> On 9/29/25 12:51, Maurizio Cimadamore wrote:
>> The point remains: lazy doesn't imply "only one" -- so, I don't buy that
>> Lazy<T> is "good enough" (although I agree it's much shorter to type --
>> which is, I think, the real argument here?)
>
> I have two points about this (and yes, naming is hard):
> - First, why does "lazy" not imply "only once"? We already have an
> example of such an API: records. Does the word "record" explicitly
> imply that its components can only be assigned once? We implemented
> and documented it that way. Similarly, we can document the behavior
> for the new interface/class to clarify that laziness also means
> "stability".
Record components are not "lazy" -- they are assigned at record
creation. Also, record is a language feature. Sure there's an API as
well (Record) but it's very unlikely for users to use that directly. So
I'm not sure how much of a precedent that is.
> - Second, the main argument here is about the "Constant" part. The
> API is being compared to the final keyword and is positioned as an
> improvement, since it allows lazy initialization while still enabling
> the JVM to optimize everything. This optimization capability is
> emphasized by the "Constant" in the name. But if the field itself is
> not declared final, that undermines the whole argument.
Final fields need to be initialized in the constructor/static
initializer. Mutable fields can be initialized wherever you want, but
then they don't get any stability guarantees. Lazy constants define
(_through an API_) a holder for a new kind of field that can be
initialized lazily, but only once (so, it is stable).
The fact that, to get perfect runtime performance you need final on the
declaration of the LazyConstant field seems, frankly, secondary -- and
derives from the choice of modelling this as an API. Perhaps, in the
table where we list the main differences between final fields, mutable
fields and lazy constants we could drop the column on constant folding.
Aside from that reference, I don't think the JEP is so focussed on
performance and low-level details as you seem to imply? The only
reference to constant folding happens quite late in the game:
> There is, furthermore, mechanical sympathy between lazy constants and
the Java runtime. Under the hood, the content of a lazy constant is
stored in a non-|final| field annotated with the JDK-internal |@Stable|
<https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/jdk/internal/vm/annotation/Stable.java#L30-L90>
annotation. This annotation is a common feature of low-level JDK code.
It asserts that, even though the field is non-|final|, the field’s value
will not change after the field’s initial and only update. This allows
the JVM to treat the content of a lazy constant as a true constant,
provided that the field which refers to the lazy constant is |final|.
Thus the JVM can apply constant-folding optimizations to code that
accesses immutable data through multiple levels of lazy constants, e.g.,
|Application.orders().getLogger()|.
Which seems fair, and even states that the lazy constant field needs to
be final for this to happen (as you pointed out).
>
>
> As for the current proposal, it feels somewhat unnatural that the API
> is split between LazyConstants and Map/List. I think it needs some
> time and experimentation to see how it plays out in practice.
Sure, we need to see how this plays out.
>
>
>> When you have lots of fields, using a lazy list provides quite a big win
>> compared to having separate holders (we made experiments with our
>> jextract tool which demonstrate this) -- because you can have a single
>> lambda/class for _all_ the elements. So you get the same performance as
>> having one holder class for each element, w/o really having one holder
>> for each element.
>
> This argument is overly focused on performance. It suggests that
> exposing many unrelated fields in a class is acceptable if it leads to
> better performance. While that might be beneficial for generated code,
> in manually written code, clarity might be more important than
> performance.
I was replying to your own performance observation that LazyConstant and
holder classes are the same. The answer is, I think, it depends. There
will be cases where you have completely unrelated fields, and so you
don't want to model these fields with a list/map -- there will be other
cases where your class has 42 similar method handles, one for each
entity it wants to represent -- at which point the advantage of
representing each field separately seems much less obvious. The
LazyConstant API gives you both options.
>
> I hope we can achieve both goals: good performance and proper
> encapsulation.
You need to define what you mean by "encapsulation" here -- we're
talking about private fields in a class after all, so it's not entirely
clear to me what do you mean by "expose" and "encapsulation".
I believe what you are after is a way to write the field declaration as
regular field declaration (perhaps with a keyword sprinkled on top), and
let the compiler worry about desugaring the code in a way that doesn't
need an holder class per field.
Fair, but we're in language territory again.
Maurizio
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/leyden-dev/attachments/20250929/b127dc41/attachment.htm>
More information about the leyden-dev
mailing list