简体   繁体   中英

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 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.

Thank you in advance.

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.

The problem as it stated now is contradiction with the Single responsibility principle , which the first principle of SOLID.

If you aim to write clean a maintainable code, 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());
}

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, then you have to stick with the first approach.

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());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM