Использование поставщика Java 8 в потоках для ленивой оценки

Я пытаюсь выполнить ленивую оценку с помощью поставщика в таком потоке

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

Но это вызовет метод getExrableList () для каждого элемента в списке. Я стараюсь не быть многословным и не хочу писать что-то подобное, т.е. добавлять непустые чеки и прочее.

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

в чем конкретно ваш вопрос? Можно упростить .. Я стараюсь не быть многословным и не хочу писать что-то подобное, т.е. добавлять непустые чеки и прочее.?

Naman 21.08.2018 05:53

Можешь просто вызвать в Set<String> expensiveStuff = new HashSet<>(getExpensiveList().get());. Использование набора позволит масштабировать его до любого практического размера с постоянным временем выполнения.

Bohemian 21.08.2018 06:25

Небольшое примечание: экземпляр поставщика после того, как он был использован, предотвращает его сборку мусора и для параллельного выполнения потоков, вы можете проверить эту ссылку logicbig.com/tutorials/core-java-tutorial/java-util-stream/… Как указал @Hoopje, лучше использовать ваш подробный код.

Prakhar Nigam 21.08.2018 08:15

Какой смысл здесь использовать Supplier? Вам нужно уже получить список для первого элемента, поэтому лень крошечная по сравнению с усложнением вашего кода.

Holger 21.08.2018 09:13
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
4
3 427
2

Ответы 2

Я не думаю, что это возможно с использованием только стандартных классов Java. Но вы можете написать свой собственный ленивый оценщик и использовать его, например (непроверенный):

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

Есть и другие возможности, см., Например, этот вопрос.

Но будьте осторожны! Если вы добавите ленивую оценку, у вас будет изменяемая структура данных, поэтому, если вы используете ее в многопоточной среде (или параллельном потоке), добавьте синхронизацию.

Но я бы использовал вашу подробную версию. Сразу понятно, что он делает, и он всего на четыре строчки длиннее. (В реальном коде я ожидаю, что эти четыре строки не имеют значения.)

Я так и думал!!. Просто хотел проверить, придумал ли кто-нибудь решение, которое является кратким и функциональным.

Amardeep 21.08.2018 09:09

Второй вариант можно упростить до

List<String> list = getList();
if (!list.isEmpty()) {
    list.stream()
        .filter(getExpensiveList().get()::contains)
        .forEach(System.out::println);
}

Это делает использование Supplier бессмысленным, поскольку даже вызов getExpensiveList() будет выполняться только тогда, когда список не пуст. Но с другой стороны, это максимальная лень, которую вы можете получить в любом случае, то есть не запрашивать дорогостоящий список, когда другой список пуст. В любом случае дорогостоящий список будет запрошен для первого элемента уже, когда список не пуст.

Если список дорогих может стать большим, вам следует использовать

List<String> list = getList();
if (!list.isEmpty()) {
    list.stream()
        .filter(new HashSet<>(getExpensiveList().get())::contains)
        .forEach(System.out::println);
}

вместо этого, чтобы избежать повторного линейного поиска. Или переделайте getExpensiveList(), чтобы в первую очередь вернуть Set.

Причина добавления второго фрагмента кода заключалась в том, чтобы объяснить мой вопрос (первый фрагмент), а не для улучшения его. Я думаю, что ваши ответы не добавляют ценности моему заданному вопросу. В любом случае спасибо за ответ.

Amardeep 21.08.2018 11:27

Разве это не вызывает getExpensiveList().get() для каждого элемента потока?

fps 21.08.2018 17:02

@Amardeep Я решил предоставить улучшенный вариант вашего второго фрагмента кода, потому что он выглядит более сложен в вашем вопросе, но на самом деле проще, чем ваш первый вариант. Как уже объяснялось в моем ответе, ваш первый вариант не дает никакой реальной пользы, т.е. касательно желаемой лени. Так что есть вариант попроще, но без недостатков по сравнению с другими. Если это не ответ, возможно, вы задаете неправильный вопрос.

Holger 21.08.2018 17:05

@FedericoPeraltaSchaffner нет, ссылка на метод формы expression::name будет оценивать выражение один раз и фиксировать результат. См. Также Какое эквивалентное лямбда-выражение для System.out :: println

Holger 21.08.2018 17:06

@FedericoPeraltaSchaffner Да, как я уже отмечал в своем вопросе, будет. И в довершение всего это послужило поводом для постановки этого вопроса.

Amardeep 22.08.2018 02:23

@Amardeep нет, не будет. Это фундаментальное различие между ссылкой на метод в форме getExpensiveList().get())::contains и лямбда-выражением, например s -> expensiveListSupplier.get().contains(s).

Holger 22.08.2018 08:08

Требуется ли условие !list.isEmpty()?

user7 26.09.2018 14:55

@ user7 не для потока, а чтобы избежать вызова getExpensiveList() и getExpensiveList().get(). Ссылка на метод в форме expression::name, как и getExpensiveList().get()::contains, немедленно оценит expression и зафиксирует результат, поэтому он выполняется не для каждого элемента потока, а только один раз. Но это также подразумевает, что здесь он будет вычислен ровно один раз, даже если список пуст - если только мы не обернем весь оператор if (!list.isEmpty()). Конечно, если выражение дешевое, как в случае с System.out::println, нам не о чем беспокоиться.

Holger 26.09.2018 15:10

Понятно. Спасибо за объяснение :)

user7 26.09.2018 15:31

Другие вопросы по теме