How to log filtered values in Java Streams
Solution 1
If you want to integrate it with Stream API, there's not much you can do other than introducing the logging manually. The safest way would be to introduce the logging in the filter()
method itself:
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());
Keep in mind that introduction of side-effects to Stream API is shady and you need to be aware of what you're doing.
You could also construct a generic wrapper solution:
private static <T> Predicate<T> andLogFilteredOutValues(Predicate<T> predicate) {
return value -> {
if (predicate.test(value)) {
return true;
} else {
System.out.println(value);
return false;
}
};
}
and then simply:
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());
...or even make the action customizable:
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;
}
};
}
then:
List<Person> filtered = persons.stream()
.filter(andLogFilteredOutValues(p -> !"John".equals(p.getName()), System.out::println))
.collect(Collectors.toList());
Solution 2
You could use
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);
or, if you prefer a single-statement form:
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);
}));
Solution 3
As there's no way to run terminal actions on elements matching opposite filters on the same stream, the best option may be just to use a condition in a peek
's consumer.
That avoids traversing the stream/collection twice.
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());
};
Then we can pass that consumer to peek
, only filtering for the ultimate action after that:
List<Person> nonJohns = persons.stream()
.peek(logger)
.filter(p -> !"John".equals(p.getName()))
.collect(Collectors.toList());
Related videos on Youtube
Comments
-
Ravi over 1 year
I have a requirement to
log/sysout
the filtered values in Java Streams. I am able tolog/sysout
the non-filtered value usingpeek()
method. However, can someone please let me know how to log filtered values?For example, let's say I have a list of
Person
objects like this:List<Person> persons = Arrays.asList(new Person("John"), new Person("Paul"));
I want to filter out those persons who are not "John" as follows:
persons.stream().filter(p -> !"John".equals(p.getName())).collect(Collectors.toList());
However, I have to log the details of that "John" person which is filtered. Can someone please help me achieve this?
-
Brian Goetz almost 6 yearsCollect with
partitioningBy()
. -
Holger almost 6 years@BrianGoetz well, yes, that’s what my answer already suggests… ;-)
-
-
Lino almost 6 yearsthis prints a list, which is not really a bummer. Still a cool answer, didn't thought of that :)
-
Holger almost 6 years@Lino: it’s easy to change
System.out.println("filtered: " + map.get(true));
tomap.get(true).forEach(x -> System.out.println("filtered: " + x));
if you prefer that form. -
Eugene almost 6 years@Holger ... but this will collect unnecessarily to a
Map
first, obviously, if the OP cares. I understand it's either collecting toMap
or aList
, just saying -
Holger almost 6 years@Eugene sure, for just printing the elements, it’s some overhead, if there’s a larger number of skipped elements (the accepted elements get into a
List
anyway). On the other hand, the typical follow-up question is “how can I get hands on the filtered elements to do more than printing?” So that’s the answer guiding to solutions to the typical follow-up tasks. It doesn’t invalidate the other answers which specifically address the printing-only aspect. -
Michael almost 6 yearsYou are misusing
peek
. From the documentation: "This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline". If you change the terminal operation to one that's short-circuiting, this will stop working properly. -
ernest_k almost 6 years@Michael I know, but that's not worse than doing it in a filter predicate. I understand the intent behind the method, but the API doesn't support this use case without leading to an additional stream traversal. Pretty much any workaround is okay, but one intermediate operation taking a consumer is the least bad one, imo.
-
ernest_k almost 6 years@Michael thanks for the observation about putting the warning in the answer, I'll be sure to do that in future. I believe there are many ways to look at this, but we both know that this "logging" need is too simple to make deep analyses necessary. But thanks for your comments
-
davesbrain over 2 yearsI like this... but you went to all the trouble of making the action generic but still reference logging. The name could be "filterWithAction", "withAction" etc...