Search code examples
javalistfunctional-programmingjava-stream

Java-Stream - Combine results of two methods producing a Value and a List invoked conditionaly inside the same stream operation in Java 8


I have two methods: funca() and funcb() which return a value of type X or a List<X> respectively like shown below:

X funca(Event e) { ... }

List<X> funcb(Event e) { ... }

I want to use them in the Stream and collect the result into a list.

These method methods should be called under different conditions, like shown below in pseudocode:

List<Event> input = // initializing the input

List<X> resultList = input.values().stream()
    .(event -> event.status=="active" ? funca(event) : funcb(event))
    .collect(Collectors.toList());

Can someone please tell me how I can achieve this so that whether the function returns a list of values or values?


Solution

  • Since one of your functions produces a Collection as a result, you need a stream operation that allows performing one-to-many transformation. For now, Stream IPA offers two possibilities: flatMap() and mapMulti().

    To approach the problem, you need to take a closer look at them and think in terms of these operations.

    flatMap()

    This operation requires a function producing a Stream, and elements of this new stream become a replacement for the initial element.

    Therefore, you need to wrap the result returned by the funca() with Singleton-Stream using Stream.of() (there's no need for wrapping the element with a List, like shown in another answer flatMap() is not capable to consume Collections).

    List<X> = input.values().stream()
        .flatMap(event -> "active".equals(event.getStatus()) ? 
            Stream.of(funca(event)) : funcb(event).stream()
        )
        .toList(); // for Java 16+ or collect(Collectors.toList())
    

    mapMulti()

    This operation was introduced with Java 16 and is similar to flatMap() but acts differently.

    Contrary to flatMap it doesn't consume a new Stream. As an argument it expects a BiConsumer. Which in turn takes a stream element and a Consumer of the resulting type. Every element offered to the Consumer becomes a part of the resulting stream.

    mapMulti() might be handy if funcb() produces a list which is very moderate in size (refer to documentation linked above for more details), otherwise flatMap() would be the right tool.

    List<X> = input.values().stream()
        .<X>mapMulti((event, consumer) -> {
            if ("active".equals(event.getStatus())) consumer.accept(funca(event));
            else funcb(event).forEach(consumer);
        })
        .toList(); // for Java 16+ or collect(Collectors.toList())
    

    Sidenote: don't use == to compare reference types (like String) unless you need to make sure that both references are pointing to the same object, use equals() method instead.