[External] : Re: Feedback about LazyConstants API (JEP526)
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Tue Dec 9 11:50:48 UTC 2025
An optional-returning tryGet is one of the possible ideas floating
around, yes.
Maurizio
On 09/12/2025 11:37, Red IO wrote:
> You can implement orElse in combination with a test function that
> returns a boolean rather the value is there already. I don't see
> orElse as such a great primitive.
> At this point I want to advertise my tryGet suggestion that returns an
> Option again as I think it would satisfy the people wanting orElse
> without the confusion orElse created in this discussion alone.
> Also in this sense tryGet is the more pure form of the "primitive" as
> it returns basically a tuple of the value and a boolean rather the
> value is there. Which is pretty much the entire state the LazyConstant
> is carrying without any transformation of information.
>
> Great regards
> RedIODev
>
>
> On Tue, Dec 9, 2025, 11: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/077decae/attachment-0001.htm>
More information about the amber-dev
mailing list