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