Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to log filtered values in Java Streams

I have a requirement to log/sysout the filtered values in Java Streams. I am able to log/sysout the non-filtered value using peek() 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?

like image 417
Ravi Avatar asked May 24 '18 05:05

Ravi


4 Answers

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()); 
like image 131
Grzegorz Piwowarek Avatar answered Sep 16 '22 20:09

Grzegorz Piwowarek


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);         })); 
like image 28
Holger Avatar answered Sep 16 '22 20:09

Holger


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());
like image 42
ernest_k Avatar answered Sep 16 '22 20:09

ernest_k


One thing I like to do is use a private method to do the logging and to supply the predicate.

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;
}
like image 41
davesbrain Avatar answered Sep 17 '22 20:09

davesbrain