如何在 Java 流中记录筛选的值



我需要在Java Streams中log/sysout过滤后的值。我能够使用peek()方法log/sysout未过滤的值。 但是,有人可以告诉我如何记录过滤后的值吗?

例如,假设我有一个Person对象列表,如下所示:

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

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

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

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

如果要将其与 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());

请记住,将副作用引入流 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;
}

最新更新