简体   繁体   中英

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());
    }

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.)

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, ie 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM