如何在Java Streams中记录过滤的值

Rav*_*avi 15 java java-8 java-stream

我需要log/sysoutJava Streams中的过滤值.我能够log/sysout使用非过滤值的peek()方法.但是,有人可以让我知道如何记录过滤值?

例如,假设我有一个Person像这样的对象列表:

List<Person> persons = Arrays.asList(new Person("John"), new Person("Paul"));
Run Code Online (Sandbox Code Playgroud)

我想过滤掉那些不是"约翰"的人如下:

persons.stream().filter(p -> !"John".equals(p.getName())).collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

但是,我必须记录被过滤的"约翰"人的详细信息.有人可以帮助我实现这个目标吗?

Grz*_*rek 15

如果要将其与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());
Run Code Online (Sandbox Code Playgroud)

请记住,对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;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

然后简单地说:

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());
Run Code Online (Sandbox Code Playgroud)

......甚至可以自定义动作:

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;
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

然后:

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


Hol*_*ger 14

你可以用

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);
Run Code Online (Sandbox Code Playgroud)

或者,如果您更喜欢单一陈述形式:

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);
        }));
Run Code Online (Sandbox Code Playgroud)

  • @Lino:很容易改变`System.out.println("filtered:"+ map.get(true));`到`map.get(true).forEach(x - > System.out.println("过滤) :"+ x));`如果你喜欢那种形式. (2认同)
  • @Eugene肯定,只是打印元素,这是一些开销,如果有更多的跳过元素(接受的元素无论如何进入`List`).另一方面,典型的后续问题是"如何才能让过滤后的元素更多地进行打印?"这就是指导解决典型后续任务的答案.它不会使专门解决仅打印方面的其他答案无效. (2认同)

ern*_*t_k 7

由于无法对匹配同一流上相反过滤器的元素执行终端操作,因此最好的选择可能只是使用使用者中的条件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());
};
Run Code Online (Sandbox Code Playgroud)

然后,我们可以将该消费者传递给peek,仅在此之后过滤最终操作:

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

  • 你在滥用`peek`。来自文档:“此方法存在**主要是为了支持调试**,您希望在其中看到元素流过管道中的某个点”。如果您将终端操作更改为短路操作,这将停止正常工作。 (2认同)