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 <[email protected]>
*Sent:* Friday, December 5, 2025 2:51 PM
*To:* Red IO <[email protected]>
*Cc:* david Grajales <[email protected]>; Per-Ake Minborg <[email protected]>; amber-dev <[email protected]>; core-libs-dev <[email protected]>
*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 <[email protected]> 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
    <[email protected]> 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
        ([email protected]) 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 <[email protected]> on behalf
            of david Grajales <[email protected]>
            *Sent:* Friday, December 5, 2025 5:38 AM
            *To:* amber-dev <[email protected]>;
            [email protected] <[email protected]>
            *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

Reply via email to