How to log filtered values in Java Streams

12,060

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());
Share:
12,060

Related videos on Youtube

Ravi
Author by

Ravi

Java enthusiast

Updated on September 15, 2022

Comments

  • Ravi
    Ravi over 1 year

    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?

    • Brian Goetz
      Brian Goetz almost 6 years
      Collect with partitioningBy().
    • Holger
      Holger almost 6 years
      @BrianGoetz well, yes, that’s what my answer already suggests… ;-)
  • Lino
    Lino almost 6 years
    this prints a list, which is not really a bummer. Still a cool answer, didn't thought of that :)
  • Holger
    Holger almost 6 years
    @Lino: it’s easy to change System.out.println("filtered: " + map.get(true)); to map.get(true).forEach(x -> System.out.println("filtered: " + x)); if you prefer that form.
  • Eugene
    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 to Map or a List, just saying
  • Holger
    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
    Michael almost 6 years
    You 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
    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
    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
    davesbrain over 2 years
    I 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...