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