Как регистрировать отфильтрованные значения в потоках 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;
}