Gabriel C
Gabriel C

Reputation: 1200

What is the most elegant way to run a lambda for each element of a Java 8 stream and simultaneously count how many elements were processed?

What is the most elegant way to run a lambda for each element of a Java 8 stream and simultaneously count how many items were processed, assuming I want to process the stream only once and not mutate a variable outside the lambda?

Upvotes: 3

Views: 1271

Answers (4)

Manoel Campos
Manoel Campos

Reputation: 1001

Streams are lazily evaluated and therefore processed in a single step, combining all intermediate operations when a final operation is called, no matter how many operations you perform over them.

This way, you don't have to worry because your stream will be processed at once. But the best way to perform some operation on each stream's element and count the number of elements processed depends on your goal. Anyway, the two examples below don't mutate a variable to perform that count.

Both examples create a Stream of Strings, perform a trim() on each String to remove blank spaces and then, filter the Strings that have some content.

Example 1

Uses the peek method to perform some operation over each filtered string. In this case, just print each one. Finally, it just uses the count() to get how many Strings were processed.

Stream<String> stream = 
       Stream.of(" java", "", " streams", "   are", " lazily ", "evaluated"); 
long count = stream
    .map(String::trim)
    .filter(s -> !s.isEmpty())
    .peek(System.out::println)
    .count();

System.out.printf(
      "\nNumber of non-empty strings after a trim() operation: %d\n\n", count);

Example 2

Uses the collect method after filtering and mapping to get all the processed Strings into a List. By this way, the List can be printed separately and the number of elements got from list.size()

Stream<String> stream = 
    Stream.of(" java", "", " streams", "   are", " lazily ", "evaluated"); 
List<String> list = stream
    .map(String::trim)
    .filter(s -> !s.isEmpty())
    .collect(Collectors.toList());

list.forEach(System.out::println);
System.out.printf(
      "\nNumber of non-empty strings after a trim() operation: %d\n\n", list.size());  

Upvotes: 0

Holger
Holger

Reputation: 298283

It might be tempting to use

long count = stream.peek(action).count();

and it may appear to work. However, peek’s action will only be performed when an element is being processed, but for some streams, the count may be available without processing the elements. Java 9 is going to take this opportunity, which makes the code above fail to perform action for some streams.

You can use a collect operation that doesn’t allow to take short-cuts, e.g.

long count = stream.collect(
    Collectors.mapping(s -> { action.accept(s); return s; }, Collectors.counting()));

or

long count = stream.collect(Collectors.summingLong(s -> { action.accept(s); return 1; }));

Upvotes: 6

Eugene
Eugene

Reputation: 120878

I would go with a reduce operation of some sort, something like this:

 int howMany = Stream.of("a", "vc", "ads", "ts", "ta").reduce(0, (i, string) -> {
        if (string.contains("a")) {
            // process a in any other way
            return i+1;
        }
        return i;
    }, (left, right) -> null); // override if parallel stream required

    System.out.println(howMany);

Upvotes: 2

m4tt
m4tt

Reputation: 102

This can be done with peek function, as it returns a stream consisting of the elements of this stream, additionally performing the provided action on each element as elements are consumed from the resulting stream.

AtomicInteger counter = new AtomicInteger(0);

elements 
  .stream()
  .forEach(doSomething())
  .peek(elem -> counter.incrementAndGet());

int elementsProcessed = counter.get();

Upvotes: 0

Related Questions