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