2

In our code base, we have several static or instance members that we would like to initialize lazily. If the initialization cannot yield null, It's easy to implement.

Otherwise, one could use an additional boolean field that indicates whether the field is initialized, but that becomes noisy for many lazy initialized fields.

Another option is to make the field Optional and use null and Optional.empty() to distinguish the uninitialized and initialized-to-null cases, but that is very ugly.

To improve this, I now consider using a Lazy<T> type, which is constructed with a Supplier<T> and has a T get() method to retrieve the result, computing it on the first access only.

An implementation in Java can be found in Vavr. For C#, there is an implementation of Lazy in the system library.

Now I noticed that Vavr's implementation is marked as deprecated for the following reason:

Java isn't a lazy evaluated language. This implementation is ineffective because it is a wrapper. It does not scale well.

But isn't that the case for all functors and monads in Java? Should we stop using them all?

I understand that it's probably a bad idea wrapping every field in a Lazy container, but having just a few of them should be fine, especially since the objects they wrap are typically large so the thin wrapper around will not make much difference in terms of memory usage.

So what is the best way to implement lazy initialization in Java? Are there better alternatives to Lazy? Can someone elaborate why it was deprecated in Vavr?

4
  • I would consider avoiding functors and monads in Java...
    – user253751
    Oct 1, 2020 at 12:54
  • I stand by what I said. I've seen lots of spaghetti code constructed with Stream and even Optional. (Not CompletionStage, yet)
    – user253751
    Oct 1, 2020 at 14:09
  • I was thinking about widely used classes such as Stream, Optional, CompletionStage etc. So while you can avoid them if you don't like them, they have become an important part of Java already. Also, the point of Vavr is to bring key Scala API to Java, which includes many more monads and functors such as Either, Try etc, so Vavr's philosophy cetrainly doesn't go against functors and monads.
    – mperktold
    Oct 1, 2020 at 14:52
  • 1
    You can misuse them for sure, but the same is true for ifs and loops and every other programming construct. All of them are useful if used in the right way and context, and neither should be overused.
    – mperktold
    Oct 1, 2020 at 14:58

1 Answer 1

4

There is nothing wrong with lazy initialization. In fact, that's a very common use case. I've written Lazy<T> helpers myself, and lazy initialization is one of the better uses of the singleton pattern.

So do go ahead and use or create such a lazy initialization wrapper. This is much more elegant and much less bug-prone than manually checking whether an isInitialized flag is set. Just be aware that there can be difficulties if multiple threads try to perform initialization.

Vavr seems to be targeted at general functional programming tools in Java, not just at useful utilities like lazy initialization. In some functional programming languages like Haskell, laziness allows for some nice tricks.

  • For example, we don't strictly need an if condition then a else b “control flow” construct because function arguments are evaluated lazily. Instead, we could declare a function that can be called like if cond a b, and Haskell would only evaluate the correct argument.
  • We can also define an expression undefined = undefined (the value of the expression is the value of the expression) which would not work in a language like Java: perhaps a compiler would reject this, perhaps we'd get an initial value like null, or the computation wouldn't terminate and we'd get a stack overflow error. Not so in Haskell, where the compiler can ignore this recursion due to laziness. Here, undefined is an expression that never evaluates to a value, and can be used as a placeholder anywhere!
  • Laziness is also super useful for other computations that don't ever terminate, e.g. infinite streams or fractals.

Using laziness in this manner wouldn't quite work in Java, because when you call a function in Java that function will be called, and infinite recursion will lead to a stack overflow. It seems that the Vavr authors don't want to propagate a style of programming that could easily lead to problems, so they've deprecated the Lazy type. But while lazy computation might not be a good fit for Java in general, it's typically still a very good fit for the more specific problem of initialization.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.