Как регистрировать отфильтрованные значения в потоках Java

У меня есть требование к log/sysout отфильтрованные значения в потоках Java. я смогу log/sysout нефильтрованное значение с использованием peek() метод. Тем не менее, может кто-нибудь, пожалуйста, дайте мне знать, как регистрировать отфильтрованные значения?

Например, допустим, у меня есть список Person объекты как это:

List<Person> persons = Arrays.asList(new Person("John"), new Person("Paul"));

Я хочу отфильтровать тех людей, которые не являются "Джоном", следующим образом:

persons.stream().filter(p -> !"John".equals(p.getName())).collect(Collectors.toList());

Тем не менее, я должен записать детали этого "Джона" человека, который отфильтрован. Может кто-нибудь, пожалуйста, помогите мне достичь этого?

4 ответа

Решение

Если вы хотите интегрировать его с Stream API, вы ничего не можете сделать, кроме как ввести запись в журнал вручную. Самый безопасный способ - ввести регистрацию в filter() сам метод:

List<Person> filtered = persons.stream()
      .filter(p -> {
          if (!"John".equals(p.getName())) {
              return true;
          } else {
              System.out.println(p.getName());
              return false;
          }})
      .collect(Collectors.toList());

Имейте в виду, что введение побочных эффектов в Stream API является неясным, и вам необходимо знать, что вы делаете.


Вы также можете создать универсальное решение для оболочки:

private static <T> Predicate<T> andLogFilteredOutValues(Predicate<T> predicate) {
    return value -> {
        if (predicate.test(value)) {
            return true;
        } else {
            System.out.println(value);
            return false;
        }
    };
}

а потом просто:

List<Person> persons = Arrays.asList(new Person("John"), new Person("Paul"));

List<Person> filtered = persons.stream()
  .filter(andLogFilteredOutValues(p -> !"John".equals(p.getName())))
  .collect(Collectors.toList());

... или даже сделать действие настраиваемым:

private static <T> Predicate<T> andLogFilteredOutValues(Predicate<T> predicate, Consumer<T> action) {
    Objects.requireNonNull(predicate);
    Objects.requireNonNull(action);

    return value -> {
        if (predicate.test(value)) {
            return true;
        } else {
            action.accept(value);
            return false;
        }
    };
}

затем:

List<Person> filtered = persons.stream()
  .filter(andLogFilteredOutValues(p -> !"John".equals(p.getName()), System.out::println))
  .collect(Collectors.toList());

Вы могли бы использовать

Map<Boolean,List<Person>> map = persons.stream()
    .collect(Collectors.partitioningBy(p -> "John".equals(p.getName())));
System.out.println("filtered: " + map.get(true));
List<Person> result = map.get(false);

или, если вы предпочитаете форму с одним заявлением:

List<Person> result = persons.stream()
    .collect(Collectors.collectingAndThen(
        Collectors.partitioningBy(p -> "John".equals(p.getName())),
        map -> {
            System.out.println("filtered: " + map.get(true));
            return map.get(false);
        }));

Поскольку невозможно выполнить действия терминала для элементов, соответствующих противоположным фильтрам в одном потоке, лучшим вариантом может быть просто использование условия в peekпотребитель.

Это позволяет избежать пересечения потока / коллекции дважды.

List<Person> persons = Arrays.asList(new Person("John"), new Person("Paul"));

//A consumer that only logs "John" persons
Consumer<Person> logger = p -> {
    if ("John".equals(p.getName())) //condition in consumer
        System.out.println("> " + p.getName());
};

Тогда мы можем передать этого потребителя peek, только фильтрация для окончательного действия после этого:

List<Person> nonJohns = persons.stream()
                  .peek(logger)
                  .filter(p -> !"John".equals(p.getName()))
                  .collect(Collectors.toList());

Мне нравится использовать частный метод для ведения журнала и предоставления предиката.

      var johns = persons.stream()
    .filter(Objects::nonNull)
    .filter(this::validNameFilter)
    .collect(Collectors.toList());

private boolean validNameFilter(@NonNull Person person) {
    if ("john".equalsIgnoreCase(person.getName())) {
        return true;
    }
    log.warning("Non compliant name found {}", person.getName());
    return false;
}
Другие вопросы по тегам