jakub_k
jakub_k

Reputation: 99

Counting AND printing values in Java stream

I am writing a code, that runs a collection through a stream, and in the stream it filters the data with several filter functions, and then prints the result of filtering:

List<String> someList = Arrays.asList("string1", "string2", etc.);

someList.stream().filter(s -> predicate1()).filter(s -> predicate2()).forEach(System.out::println);

and what would greatly help me, would be the ability to print and count filtered elements. Since forEach() uses consumer function, and returns Void type, I cannot do anything further to the data. But if I change forEach() with count(), I receive a long value, and stream is ended this way or another. Is there any elegant way to have something like:

long counted = someList.stream().filter(s -> predicate1()).filter(s -> predicate2()).map??reduce??(printAndAlsoCountPrintedAndReturnFinalCountValue())

in one stream without having to run it 2 times, first with count(), and the second with forEach(System.out::println)? I was thinking about some mapper function, but map() also returns Stream<R> data, so even if I did some mapping printing-counting function, I don't see how I'd have to return counted value into a parameter.

Generally it's not a problem to run the collection through 2 streams, but I believe it's not very resource-wise, depending on how heavy the filtering functions would be, that's why I am looking for more "clever" solution, if it exists.

Upvotes: 1

Views: 3536

Answers (3)

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 28978

If you need to see all the elements that passed all the filters in the pipeline on the console, you can use peek(). This method was introduces in the Stream API for debugging.

long counted = someList.stream()
    .filter(s -> predicate1())
    .filter(s -> predicate2())
    .peek(System.out::println)
    .count();

Note: that this method should be used only for debugging purposes. If let's say instead of printing you would decide to store these elements into a collection, then you need to be aware that it's discouraged by API the documentation.

And, also, pay attention to the documentation of peek()

This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline

In cases where the stream implementation is able to optimize away the production of some or all the elements (such as with short-circuiting operations like findFirst, or in the example described in count()), the action will not be invoked for those elements.

That means when have in mind something more important than printing elements on the console (and you don't want this action to be optimized away), then you can generate a collection as a result. And then you can do whatever you need with its contents and find out its size as well.

public List<String> foo(List<String> source) {
    
    return source.stream()
        .filter(s -> predicate1())
        .filter(s -> predicate2())
        .toList(); // for Java 16+ or collect(Collectors.toList());
}

Upvotes: 3

magicmn
magicmn

Reputation: 1914

It's not really intended to be used for that, but you could use an AtomicLong.

    List<String> someList  = Arrays.asList("string1", "string2", "s1", "s2");
    AtomicLong count = new AtomicLong();
    someList .stream()
            .filter(s -> s.contains("string"))
            .forEach(s -> {
                System.out.println(s);
                count.incrementAndGet();
            });
    System.out.println(count.get());

Upvotes: 2

Mark Bramnik
Mark Bramnik

Reputation: 42451

You can use reduce function:


import java.util.Arrays;
class HelloWorld {
    public static void main(String[] args) {
        var l = Arrays.asList(1,2,3,4,5,6,7,8);
        var count= l.stream()
                    .filter(x -> x%2 == 0)
                    .reduce(0, (acc, elem) -> {
                        System.out.println(elem);
                        return acc + 1; 
                    });
        System.out.println("Total Count: " + count);
    }
}

This will print “Total Count: 4” as well as the elements themselves (2,4,6,8)

Note that this printing is a side effect so personally I would also consider writing this “as usual” with for-loops (in an imperative style) without streams at all.

Upvotes: 3

Related Questions