[External] : Re: Feedback about LazyConstants API (JEP526)

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Tue Dec 9 11:25:07 UTC 2025


Hit send too fast...

Yes, orElse alone can't set/compute the uninitialized value. So that's a 
problem (as you observed)

But orElse can't also be explained in terms of get() -- unless you 
accept doing a double access of the underlying storage.

Uplevelling, I believe the fact that, in the current form, neither API 
sits cleanly on top of the other is probably a part of the problem 
highlighted in this discussion.

Maurizio

On 09/12/2025 11:22, Maurizio Cimadamore wrote:
>
>
> On 09/12/2025 11:15, Anatoly Kupriyanov wrote:
>> That's seems exactly opposite. The `get` returns initial value always 
>> making the constant initialised by calling compute.
>> The `orElse(v)` is a shortcut for `c.isInitialized() ? c.get() : v`.
>
> Not really :-)
>
> c.isInitialized also calls `getAcquire` internally (so your snippet 
> would end up calling getAcquire twice in some  paths, once for 
> isInitialized, and another for get).
>
> Technically, get() can be explained as:
>
> V value = lazyConstant.orElse(sentinel)
> if (value == sentinel) throw new NoSuchElementException
>
> That performs only one call to getAcquire, not two.
>
> (We don't implement it exactly this way, but we could).
>
> Maurizio
>
>>
>> WBR, Anatoly.
>>
>> On Tue, 9 Dec 2025, 10:25 Maurizio Cimadamore, 
>> <maurizio.cimadamore at oracle.com> wrote:
>>
>>     I agree with most of the conclusions in this thread.
>>
>>
>>     One small nit is that, in reality, `orElse` is a "primitive" in
>>     disguise. E.g. you can implement `get` in terms of `orElse` but
>>     not the other way around (unless you are willing to do _two_
>>     accessed to the underlying value). So, while we could drop it, we
>>     would also lose something (which is why we decided to keep it, at
>>     least for now).
>>
>>
>>     Maurizio
>>
>>
>>
>>     On 08/12/2025 12:31, Per-Ake Minborg wrote:
>>>     So, it is nice that folks seem to agree that
>>>     |LazyConstant| should only compute and initialize its contents
>>>     from the Supplier/lambda given at declaration time. The
>>>     |orElse| method seems to blur the contours of |LazyConstant| ,
>>>     and so, as previously said, we might consider removing the
>>>     method altogether in the next preview.
>>>
>>>     It is also a fact that many have identified a need for
>>>     "something else more low-level" that supports a more imperative
>>>     programming model when working with constants that are lazily
>>>     set. We do not rule out that such a thing might appear in a
>>>     future JDK version.
>>>
>>>     Best, Per
>>>
>>>     Confidential- Oracle Internal
>>>     ------------------------------------------------------------------------
>>>     *From:* David Alayachew <davidalayachew at gmail.com>
>>>     <mailto:davidalayachew at gmail.com>
>>>     *Sent:* Friday, December 5, 2025 2:51 PM
>>>     *To:* Red IO <redio.development at gmail.com>
>>>     <mailto:redio.development at gmail.com>
>>>     *Cc:* david Grajales <david.1993grajales at gmail.com>
>>>     <mailto:david.1993grajales at gmail.com>; Per-Ake Minborg
>>>     <per-ake.minborg at oracle.com>
>>>     <mailto:per-ake.minborg at oracle.com>; amber-dev
>>>     <amber-dev at openjdk.org> <mailto:amber-dev at openjdk.org>;
>>>     core-libs-dev <core-libs-dev at openjdk.org>
>>>     <mailto:core-libs-dev at openjdk.org>
>>>     *Subject:* [External] : Re: Feedback about LazyConstants API
>>>     (JEP526)
>>>     Caveat -- I have only used the Java 25 version of this library.
>>>
>>>     I agree that the name orElse() is not intuitive. It was made
>>>     more intuitive by the existence of orElseSet(). In its absence,
>>>     changing the name makes sense.
>>>
>>>     Though, I'm definitely open to just removing the method. This is
>>>     easy enough to accomplish ourselves. Would prefer a rename though.
>>>
>>>     On Fri, Dec 5, 2025, 8:32 AM Red IO
>>>     <redio.development at gmail.com> wrote:
>>>
>>>         Hi David,
>>>         As par already said the orElse method doesn't initializes
>>>         the LazyConstant.
>>>         It just checks rather the value is init and if not calls the
>>>         supplier to get a substitute for the missing constant.
>>>         Example:
>>>         LazyConstant<String> x = LazyConstant.of(() -> "Const");
>>>         var uninit1 = x.orElse(() -> "substitute 1");
>>>         var uninit2 = x.orElse(() -> "substitute 2");
>>>         var init1 = x.get();
>>>         var init2 = x.orElse(() -> "substitute 3");
>>>         uninit1 and uninit2 get the substitute 1/2
>>>         And init1 and init2 get Const.
>>>
>>>         This is surprising if you expect it to be a way to init it
>>>         with an alternative value.
>>>
>>>         My suggestion would to make the separation clear and allow
>>>         for another use case by spliting this api in 2 parts:
>>>         One class LazyConstant
>>>         Takes a Supplier in static factory and exposes get()
>>>
>>>         And
>>>         Class LazyInit
>>>         Which takes no arguments in the static factory and takes a
>>>         supplier in the get method that gets called when get is
>>>         called for the first time.
>>>         In this case the source for the constant can be any piece of
>>>         code that has access to the LazyConstant. This might be
>>>         desired in some cases. In cases where it's not the other
>>>         version can be used.
>>>
>>>         This split makes it clear from which context the constant is
>>>         initialized from (consumer or at declaration)
>>>
>>>         Mixing those 2 or having methods that appear to do this is
>>>         rather confusing.
>>>
>>>
>>>
>>>         One solution for the "i might not want to init the constant"
>>>         case the "orElse" method is meant to be is to have a method
>>>         "tryGet" which returns Optional instead. This makes it clear
>>>         that the value might not be there and is not initialized
>>>         when calling the method. Nobody expects to init the constant
>>>         when calling orElse on a returned Optional.
>>>
>>>         My 2 suggestions here are completely independent and should
>>>         be viewed as such.
>>>
>>>         Great regards
>>>         RedIODev
>>>
>>>         On Fri, Dec 5, 2025, 13:55 david Grajales
>>>         <david.1993grajales at gmail.com> wrote:
>>>
>>>             HI Per. I pleasure to talk with you.
>>>
>>>             You are right about one thing but this actually makes
>>>             the API less intuitive and harder to read and reason about.
>>>
>>>             LazyConstant<String> foo = LazyConstant.of(() -> "hello");
>>>
>>>             void main() {
>>>                 if (someCondition()) {// asume false
>>>                     foo.get();
>>>                 }
>>>                 foo.orElse("hello2"); // ...
>>>
>>>                 println(foo.get()); // This prints "hello"
>>>             }
>>>
>>>             But if one assigns foo.orElse("hello2") to a variable,
>>>             the variable actually gets the "hello2" value.
>>>
>>>             void main() {
>>>                 if (someCondition()) {// asume false
>>>                     foo.get();
>>>                 }
>>>                 var res = foo.orElse("hello2"); // ...
>>>                 var res2 = foo.orElse("hello3");
>>>                 println(res); // This prints "hello2"
>>>                 println(res2);//This prints "hello3"
>>>             }
>>>
>>>             This is actually even more confusing and makes the API
>>>             more error prone. I personally think once initialized
>>>             the lazy constant should always return the same value
>>>             (maybe through the .get() method only), and there should
>>>             not be any possibility of getting a different
>>>             values from the same instance either in the .of() static
>>>             method or in any hypothetical instance method for
>>>             conditional downstream logic.  I guess one could achieve
>>>             the latter with the static factory method
>>>             through something like this (although less elegant)
>>>
>>>             private class Bar{
>>>                 private final LazyConstant<String> foo;
>>>                 private Bar(Some some){
>>>
>>>                     if(some.condition){
>>>                         foo = LazyConstant.of(() -> "hello");
>>>                     }else {
>>>                         foo = LazyConstant.of(() -> "hello2");
>>>                     }
>>>                 }
>>>             }
>>>
>>>             Thank you for reading. This is all I have to report.
>>>
>>>             Best regards.
>>>
>>>
>>>
>>>             El vie, 5 dic 2025 a la(s) 6:05 a.m., Per-Ake Minborg
>>>             (per-ake.minborg at oracle.com) escribió:
>>>
>>>                 Hi David,
>>>
>>>                 Thank you for trying out LazyConstant and providing
>>>                 feedback. That is precisely what previews are for!
>>>
>>>                 If you take a closer look at the specification of
>>>                 |LazyConstant::orElse,| it says that the method will
>>>                 /never trigger initialization./ And so, you
>>>                 /can/ actually be sure that in your first example,
>>>                 |foo| is always initialized to "hello" (if ever
>>>                 initialized). It is only if foo is not initialized
>>>                 that the method will return "hello2" (again, without
>>>                 initializing foo). This is similar to how
>>>                 |Optional| works.
>>>
>>>                 It would be possible to entirely remove the
>>>                 |orElse()| method from the API, and in the rare
>>>                 cases where an equivalent functionality is called
>>>                 for, rely on |LazyConstant::isInitialized| instead.
>>>
>>>                 Best, Per
>>>
>>>
>>>                 Confidential- Oracle Internal
>>>                 ------------------------------------------------------------------------
>>>                 *From:* amber-dev <amber-dev-retn at openjdk.org> on
>>>                 behalf of david Grajales <david.1993grajales at gmail.com>
>>>                 *Sent:* Friday, December 5, 2025 5:38 AM
>>>                 *To:* amber-dev <amber-dev at openjdk.org>;
>>>                 core-libs-dev at openjdk.org <core-libs-dev at openjdk.org>
>>>                 *Subject:* Feedback about LazyConstants API (JEP526)
>>>                 Dear Java Dev Team,
>>>
>>>                  I am writing to provide feedback and two specific
>>>                 observations regarding the LazyConstant API, which
>>>                 is currently a preview feature in OpenJDK 26.
>>>
>>>                  I appreciate the API's direction and I think it's a
>>>                 good improvement compared to its first iteration;
>>>                 however, I see potential for improved
>>>                 expressiveness, particularly in conditional scenarios.
>>>
>>>
>>>                 *1. Proposal: Zero-Parameter `LazyConstant.of()`
>>>                 Overload:*
>>>
>>>                 Currently, the mandatory use of a factory method
>>>                 receiving a `Supplier` (due to the lack of a public
>>>                 constructor) can obscure the expressiveness of
>>>                 conditional or multiple-value initialization paths.
>>>                 **The Issue:** When looking at the declaration:
>>>
>>>                 LazyConstant<String> foo = LazyConstant.of(() ->
>>>                 "hello");
>>>
>>>                 the code gives the strong, immediate impression that
>>>                 the value is *always* initialized to |"hello"|. This
>>>                 makes it difficult to infer that the constant might
>>>                 ultimately resolve to an alternative value set later
>>>                 via |orElse()| or another conditional path,
>>>                 especially when skimming the code:
>>>
>>>                 LazyConstant<String> foo = LazyConstant.of(() ->
>>>                 "hello"); // When skimming the code it's not always
>>>                 obvious that this may not be the actual value
>>>                 void main() {
>>>                 if (someCondition()) {
>>>                           foo.get(); // Trigger initialization to
>>>                 "hello"
>>>                  }
>>>                 // If someCondition is false, the final value of foo
>>>                 is determined here:
>>>                 var res1 = foo.orElse("hello2"); // ...
>>>                 }
>>>
>>>                 *My Suggestion:* I propose introducing a
>>>                 *zero-parameter overloaded static factory method*
>>>                 |of()|:
>>>
>>>                 LazyConstant<String> foo = LazyConstant.of();
>>>
>>>                 This form explicitly communicates that the constant
>>>                 is initialized to an *unresolved* state, suggesting
>>>                 that the value will be determined downstream by the
>>>                 first invocation of an initialization/computation
>>>                 method.
>>>
>>>                 LazyConstant<String> foo = LazyConstant.of(); //
>>>                 Clearly unresolved
>>>                 void main() {
>>>                 if (someCondition()) {
>>>                       foo.orElse("hello");
>>>                  }
>>>                 var res1 = foo.orElse("hello2"); // ...
>>>                 }
>>>
>>>                 This is specially useful for clarity when one has
>>>                 conditional initialization in places such as the
>>>                 constructor of a class. For example
>>>
>>>                 private class Bar{
>>>                     LazyConstant<String> foo = LazyConstant.of();
>>>                     private Bar(Some some){
>>>                         if(some.condition()){
>>>                             foo.orElse("foo");
>>>                         }
>>>                         foo.orElse("foo2");
>>>                     }
>>>
>>>                     String computeValue() {
>>>                         return "hello";
>>>                     }
>>>
>>>                     String computeValue2(){
>>>                         return "hello2";
>>>                     }
>>>                 }
>>>
>>>
>>>                       2. Method Naming Suggestion and and supplier
>>>                       in instance method for consistency in the API
>>>
>>>                 My second, much more minor observation relates to
>>>                 the instance method |orElse(T t)|.
>>>
>>>                 While |orElse| fits a retrieval pattern, I
>>>                 personally feel that *|compute|* or
>>>                 *|computeIfAbsent|* would better express the intent
>>>                 of this method, as its primary function is not just
>>>                 to retrieve, but to trigger the computation and *set
>>>                 the final value* of the constant if it is currently
>>>                 uninitialized. Also, as the factory of() has a
>>>                 supplier i think this instance method should also
>>>                 receive a Supplier, This not only keeps the API
>>>                 consistent in the usage but makes more ergonomic the
>>>                 declaration of complex initialization logic inside
>>>                 the method.
>>>
>>>
>>>                 private class Bar{
>>>                 LazyConstant<InitParams> foo =
>>>                 LazyConstant.of(InitParam::default); // Under the
>>>                 current API this is mandatory but in reality the
>>>                 value is set in the constructor, default is never
>>>                 really used.
>>>                     private Bar(Some some){
>>>                  foo.compute(some::executeCallToCacheDBAndBringInitializationParams)
>>>                 //Real configuration happens here
>>>
>>>                     }
>>>                 }
>>>
>>>                 This last it's very common for initialization of
>>>                 configuration classes and singletons.
>>>
>>>
>>>                 Thank you so much for your attention, I hope you
>>>                 find this feedback useful.
>>>
>>>                 Always yours. David Grajales
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20251209/5af09593/attachment-0001.htm>


More information about the amber-dev mailing list