How to view intermediate results in a stream?

Viewed 100

How can I print out the result of the .collect() while it is processing?

When I try a forEach, I get a compiler error. filter and peek only apply to streams.

    var landmarks = Set.of("Eiffel Tower", "Statue of Liberty",
            "Stonehenge", "Mount Fuji");
    var result = landmarks
            .stream()
            .collect(Collectors.partitioningBy(b -> b.contains(" ")))

<---------------what to put here?

            .entrySet()
            .stream()
            .flatMap(t -> t.getValue().stream())
            .collect(Collectors.groupingBy(s -> !s.startsWith("S")));
4 Answers

If you are using IntelliJ, you can use the Trace Current Strean Chain functionality while debugging.

enter image description here

Unfortunately, it's impossible to achieve what you're asking in a concise way, since Java doesn't support extension or postfix functions. So, there is no way to add new method (e.g. .debugPrint()) into Map (the result of .collect).

( If you'd asked this question for Kotlin, the answer would've been to use .also{}. )

However, there are couple of tricks that come close.

1. The easy way

Just wrap the whole chain in the function that prints and returns the result.

  • Pros: easy to write
  • Cons: breaks the nice formatting of the chain
var result = printed(
            landmarks
                .stream()
                .collect(Collectors.partitioningBy(b -> b.contains(" ")))
        )
        .entrySet()
        .stream()
        .flatMap(t -> t.getValue().stream())
        .collect(Collectors.groupingBy(s -> !s.startsWith("S")));

// ...

// required helper function
static <T> T printed(T any) {
    System.out.println(any);
    return any;
}

2. The verbose way

You can create a function that wraps the Collector and prints it's result after it's finished collecting.

  • Pros: stream chain still looks nice
  • Cons: collector wrapper implementation is pretty verbose
var result = landmarks
        .stream()
        .collect(printed(Collectors.partitioningBy(b -> b.contains(" "))))
        .entrySet()
        .stream()
        .flatMap(t -> t.getValue().stream())
        .collect(Collectors.groupingBy(s -> !s.startsWith("S")));

// ...

// The required helper function
static <T, A, R> Collector<T, A, R> printed(Collector<T, A, R> wrapped) {
    return new Collector<T, A, R>() {
        @Override
        public Supplier<A> supplier() {
            return wrapped.supplier();
        }

        @Override
        public BiConsumer<A, T> accumulator() {
            return wrapped.accumulator();
        }

        @Override
        public BinaryOperator<A> combiner() {
            return wrapped.combiner();
        }

        @Override
        public Function<A, R> finisher() {
            return (A x) -> {
                R res = wrapped.finisher().apply(x);
                System.out.println(res);
                return res;
            };
        }

        @Override
        public Set<Characteristics> characteristics() {
            // Need to ensure IDENTITY_FINISH is removed, otherwise `finisher` is not called
            return wrapped.characteristics().stream().filter(c -> c != IDENTITY_FINISH).collect(Collectors.toSet());
        }
    };
}

Both approaches print (in java 15):

{false=[Stonehenge], true=[Mount Fuji, Eiffel Tower, Statue of Liberty]}

You can add a dummy stage to the chain with a side-effect:

public class Example {

    public static void main(String[] args) {
        var landmarks = Set.of("Eiffel Tower", "Statue of Liberty",
                "Stonehenge", "Mount Fuji");
        var result = landmarks
                .stream()
                .collect(Collectors.partitioningBy(b -> b.contains(" ")))
                .entrySet()
                .stream()
                .map( e -> {
                    System.out.println(e.getKey() + ": " + e.getValue());
                    return e;
                })
                .flatMap(t -> t.getValue().stream())
                .collect(Collectors.groupingBy(s -> !s.startsWith("S")));
    }
}

I am not sure if I fully understood the problem. Is this what yout are trying to achieve?

void test() {
    var landmarks = Set.of("Eiffel Tower", "Statue of Liberty", "Stonehenge", "Mount Fuji");

    var result1 = landmarks
        .stream()
        .collect(Collectors.partitioningBy(this::containsSpace))
        .entrySet()
        .stream()
        .flatMap(t -> t.getValue().stream())
        .collect(Collectors.groupingBy(s -> !s.startsWith("S")));
}

private boolean containsSpace(String in) {
    var contains = in.contains(" ");
    System.out.printf("%b - %s%n", contains, in);

    return contains;
}

Result:

true - Statue of Liberty
true - Mount Fuji
true - Eiffel Tower
false - Stonehenge
Related