Reputation: 378
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());
}
Upvotes: 3
Views: 10237
Reputation: 298183
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.
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.
Upvotes: 3
Reputation: 12932
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.)
Upvotes: 2