Feedback on LazyConstants (formerly StableValues)
david Grajales
david.1993grajales at gmail.com
Wed Sep 24 02:02:44 UTC 2025
Dear Amber team,
I hope this message finds you well. First of all, I want to extend my
gratitude for all your hard work in continuously improving the Java
platform — it is very much appreciated.
I have been experimenting with the StableValues API (now renamed
LazyConstants) and attempting to use it in place of some of my older
automation scripts for personal projects. While doing so, I noticed that
the current design feels a bit rigid in certain scenarios, and this may
indicate that a keyword or annotation-based solution could be a better fit.
Let me illustrate with a simple example. A common case of deferred
initialization is the lazy singleton pattern:
void main(){
var message = MySingleton.getInstance("Hello java").getMessage();
println(message);
}
private static class MySingleton{
private static MySingleton instance;
private final String message;
private MySingleton(String message) {
this.message = message;
}
public static MySingleton getInstance(String message) {
if (instance == null) {
instance = new MySingleton(message);
}
return instance;
}
public String getMessage() {
return message;
}
}
While this works, the result feels neither more concise nor more readable.
The code is now cluttered by the API’s requirements rather than improved by
them.
public class MySingleton2 {
private static final StableValue<MySingleton2> instance =
StableValue.of();
private final String message;
private MySingleton2(String message) {
this.message = message;
}
public static MySingleton2 getInstance(String message){
return instance.orElse(new MySingleton2(message));
}
public String getMessage(){
return message;
}
}
The issue here is that the current API does not offer a way to lazily
capture constructor parameters — for example, through a Function or
Supplier-based
variant. Something like this would feel more natural:
public <T, R>StableValue<R> createLazy(T t, Function<T, R> underlying){
return StableValue.of(underlying.apply(t));
}
final StableValue<MySingleton> instanceLazy = createLazy("Hello",
MySingleton::getInstance);
Now let's take a look to the case where the message field is lazy too.
public class MySingleton3 {
private static final LazyConstant<MySingleton3> instance =
LazyConstant.of(ew MySingleton3());
private LazyConstant<String> message;
private MySingleton3() {
// constructor doesn't accept the dynamic value in this pattern,
// so the message must be initialized separately.
}
public static MySingleton3 getInstance(String value) {
// obtain the singleton (or create one if absent)
MySingleton3 s = instance.orElse(new MySingleton3());
// awkward: we must initialise the instance's lazy message here to
capture `value`
if (s.message == null) {
s.message = LazyConstant.of(s.computeMessage(value));
}
return s;
}
public String getMessage() {
// we assume getInstance(...) has already set `message`; read it
with orElse(null)
// (using `null` here to avoid any extra computation as orElse
takes a concrete value).
return message.orElse(null);
}
private String computeMessage(String value) {
return "Hello " + value;
}
public static void main(String[] args) {
// prints "Computing message..." once, then "Hello Java"
println(MySingleton3.getInstance("Java").getMessage());
}
}
As you can see, there is no real improvement from a code perspective — in
fact, the resulting implementation is *harder to reason about*. The mental
model for the developer becomes more complex, since they must explicitly
manage when and how values are captured. Additionally, the lack of a simple
get() method (having only orElse(...) and orElseThrow(...)) makes the code
more verbose and slightly redundant, especially in cases where you know the
value has already been initialized.
Now, the issue is not the lack of this or other methods. The issue relies
on the infinite possible combinations that makes the design of a simple but
flexible enough API a hard to achieve task. Seeing this limitation and the
past limitations I have already shared in this mailing list, i am not sure
if an API based approach is the best solution. I understand the good thing
about API based features are how easy they are to grow, at least compared
to a language level feature, but the current approach i fear may lead to an
ever expanding API that would never be enough.
Considering this, and similar concerns I have previously shared here, I
wonder if an API-based approach is really the right long-term solution.
While API-based features are easier to evolve than language features, I
worry that this approach could lead to an ever-expanding API that still
never quite covers all the practical cases developers face. My current
impression is that the API gets in the way more than it helps. Java already
provides more natural ways to achieve lazy initialization, and LazyConstants
ends up feeling like a low-level optimization, ceremony aimed at squeezing
out marginal performance gains that may not even justify the added
complexity.
I would love to hear your thoughts, pointing me to a better direction about
how to use the API for this kind of cases. Is there something that i am not
seeing?
Thank you for your time and for continuing to improve the Java platform.
Best regards and always yours: David Grajales Cardenas.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20250923/72394309/attachment-0001.htm>
More information about the amber-dev
mailing list