Лямбда в Stream.map/filter не вызывается - PullRequest
7 голосов
/ 09 июля 2020

Я пытаюсь найти отдельные дубликаты и не дубликаты в List, добавляя их к Set и List при использовании Stream.filter и Stream.map

List<String> strings = Arrays.asList("foo", "bar", "foo", "baz", "foo", "bar");

Set<String> distinct = new HashSet<>();
List<String> extras = new ArrayList<>();

strings
  .stream()
  .filter(x -> !distinct.add(x))
  .map(extra -> extras.add(extra));

В конце я ожидаю, что distinct будет [foo, bar, baz] и extras будет [foo, foo, bar], поскольку есть 2 дополнительных экземпляра foo и 1 из bar. Однако после запуска они оба пусты.

Лямбды, передаваемые потоку, никогда не вызываются, что я проверил, пытаясь напечатать внутри не работает, когда я пытаюсь использовать put с Map. Что я делаю не так?

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

Ответы [ 4 ]

11 голосов
/ 09 июля 2020

Оба Stream#filter и Stream#map являются промежуточными операциями, что означает, что они вычисляются лениво. Согласно документации:

Промежуточные операции возвращают новый поток. Они всегда ленивы; выполнение промежуточной операции, такой как filter (), на самом деле не выполняет никакой фильтрации, а вместо этого создает новый поток, который при прохождении содержит элементы исходного потока, соответствующие заданному предикату. Обход источника конвейера не начинается до тех пор, пока не будет выполнена терминальная операция конвейера.

В любом случае вы должны использовать соответствующие методы, чтобы избежать подобных ошибок; forEach следует использовать вместо map, поскольку Stream#map используется для преобразования потока в результат вызова функции сопоставления для каждого элемента, а Stream#forEach используется для итерации по нему.

Демонстрация: https://ideone.com/ZQhLJC

strings
  .stream()
  .filter(x -> !distinct.add(x))
  .forEach(extras::add);

Другой возможный обходной путь - выполнить операцию терминала, например .collect, чтобы принудительно применить фильтр и карту, которую нужно применить.

strings
  .stream()
  .filter(x -> !distinct.add(x))
  .map(extra -> extras.add(extra)).collect(Collectors.toList());

Если вы собираетесь использовать .collect, вы можете также использовать собранный список как extras, чтобы не тратить время и пространство.

List<String> extras = strings
  .stream()
  .filter(x -> !distinct.add(x)).collect(Collectors.toList());
6 голосов
/ 09 июля 2020

Ваш код не работает, потому что поток не расходуется. Вы предоставили только промежуточные операции, но до тех пор, пока вы не вызовете завершающую операцию, например forEach, reduce или collect, ничего, что вы определили в своем потоке, не будет вызвано.

Лучше использовать peek для печати элементов, проходящих через поток, и collect для получения всех элементов в списке:

List<String> extras = strings
    .stream()
    .filter(x -> !distinct.add(x))
    .peek(System.out::println)
    .collect(Collectors.toList());

Использование forEach для заполнения пустой коллекции, созданной ранее, является запахом кода и ничего не имеет для функционального программирования.

4 голосов
/ 09 июля 2020

Чтобы применить фильтр, вам нужно вызвать операцию терминала, например collect () . В этом случае вы можете назначить элементы, которые проходят фильтр, непосредственно в список extras вместо использования функции map .

Попробуйте что-то вроде этого:

List<String> strings = Arrays.asList("foo", "bar", "foo", "baz", "foo", "bar");

Set<String> distinct = new HashSet<>();

List<String> extras = strings
                     .stream()
                     .filter(x -> !distinct.add(x))
                     .collect(Collectors.toList());
2 голосов
/ 09 июля 2020

Существует более элегантный способ использования filter с методом Predicate negate() вместо использования логического оператора !

List<String> extras = strings
.stream()
.filter(((Predicate<String>) distinct::add).negate())
.peek(System.out::println)
.collect(Collectors.toList());

peek - функция, используемая только для отладки конвейера.

...