Using Java 8 Supplier in streams to achieve lazy evaluation
Using Java 8 Supplier in streams to achieve lazy evaluation
I am trying to achieve lazy evaluation using Supplier in a stream like this
public static void main(String args)
Supplier<List<String>> expensiveListSupplier= getExpensiveList();
getList().stream()
.filter(s -> expensiveListSupplier.get().contains(s))
.forEach(System.out::println);
private static Supplier<List<String>> getExpensiveList()
return () -> Stream
.of("1", "2", "3")
.peek(System.out::println)
.collect(Collectors.toList());
private static List<String> getList()
return Stream.of("2", "3")
.collect(Collectors.toList());
But this will call getExpensiveList() method for every element in the list.
I am trying not to be verbose and don't want to write something like this ie to add not empty checks and stuff.
public static void main(String args)
Supplier<List<String>> expensiveListSupplier = getExpensiveList();
List<String> list = getList();
if (!list.isEmpty())
List<String> expensiveList = expensiveListSupplier.get();
list.stream()
.filter(expensiveList::contains)
.forEach(System.out::println);
private static Supplier<List<String>> getExpensiveList()
return () -> Stream
.of("1", "2", "3")
.peek(System.out::println)
.collect(Collectors.toList());
private static List<String> getList()
return Stream.of("2", "3")
.collect(Collectors.toList());
Can you just call
Set<String> expensiveStuff = new HashSet<>(getExpensiveList().get());
. Using a set would scale up to any practical size with constant runtime.– Bohemian♦
Aug 21 at 4:25
Set<String> expensiveStuff = new HashSet<>(getExpensiveList().get());
A small note: supplier instance after it's been used, prevents it from being garbage collected and for parallel execution of streams, you can check this link logicbig.com/tutorials/core-java-tutorial/java-util-stream/… As @Hoopje pointed better to go with your verbose code.
– Prakhar Nigam
Aug 21 at 6:15
What’s the point of using a
Supplier
here? You need to get the list for the first element already, so the laziness is tiny, compared to the complication of your code.– Holger
Aug 21 at 7:13
Supplier
2 Answers
2
I don't think this is possible using only standard Java classes. But you could write your own lazy evaluator and use that, for example (untested):
public class LazyValue<T> implements Supplier<T>
private T value;
private final Supplier<T> initializer;
public LazyValue(Supplier<T> initializer)
this.initializer = initializer;
public T get()
if (value == null)
value = initializer.get();
return value;
There are other possibilities as well, see for example this question.
But beware! If you add lazy evaluation, you have a mutable data structure, so if you use it in a multithreaded environment (or a parallel stream), add synchronization.
But, I would use your verbose version. It's immediately clear what it does, and it is only four lines longer. (In real code I expect that those four lines are irrelevant.)
I thought so!!. Just wanted to check if someone has figured out a solution which is concise and functional.
– Amardeep
Aug 21 at 7:09
You second variant can be simplified to
List<String> list = getList();
if(!list.isEmpty())
list.stream()
.filter(getExpensiveList().get()::contains)
.forEach(System.out::println);
It makes the use of a Supplier
pointless, as even the call to getExpensiveList()
will be done only when the list is non-empty. But on the other hand, that’s the maximum laziness you can get anyway, i.e. not to request the expensive list when the other list is empty. In either case, the expensive list will be requested for the first element already when the list is not empty.
Supplier
getExpensiveList()
If the expensive list can become large, you should use
List<String> list = getList();
if(!list.isEmpty())
list.stream()
.filter(new HashSet<>(getExpensiveList().get())::contains)
.forEach(System.out::println);
instead, to avoid repeated linear searches. Or redesign getExpensiveList()
to return a Set
in the first place.
getExpensiveList()
Set
The reason of adding the second code snippet was to explain my question(first snippet) and not for improvements in it. I think your answers does not add value to my intended question. Thanks anyway for answering.
– Amardeep
Aug 21 at 9:27
Doesn't this make a call to
getExpensiveList().get()
for every element of the stream?– Federico Peralta Schaffner
Aug 21 at 15:02
getExpensiveList().get()
@Amardeep I decided to provide an improved variant of your second code snippet, because it looks more complicated in your question while actually being simpler than your first variant. As already explained in my answer, your first variant does not provide any actual benefit, i.e. regarding the desired laziness. So there’s a variant which is simpler but still without any drawbacks compared to the others. If that’s not an answer, you are perhaps asking the wrong question.
– Holger
Aug 21 at 15:05
@FedericoPeraltaSchaffner no, a method reference of the
expression::name
form will evaluate the expression once and capture the result. See also What is the equivalent lambda expression for System.out::println…– Holger
Aug 21 at 15:06
expression::name
@FedericoPeraltaSchaffner Yes it will as I have pointed out in my question as well. And in top of it all this was the reason for putting up this question.
– Amardeep
Aug 22 at 0:23
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
what exactly is your question? Can you simplify .. I am trying not to be verbose and don't want to write something like this ie to add not empty checks and stuff ?
– nullpointer
Aug 21 at 3:53