Search code examples
javajava-streamflatmap

How to apply flatMap() to implement the following logic with Streams


I have the following for loop:

List<Player> players = new ArrayList<>();

for (Team team : teams) {
   ArrayList<TeamPlayer> teamPlayers = team.getTeamPlayers();
   for (teamPlayer player : teamPlayers) {
       players.add(new Player(player.getName, player.getPosition());
   }
}

and I'm trying to convert it to a Stream:

List<Player> players = teams.forEach(t -> t.getTeamPlayers()
    .forEach(p -> players.add(new Player(p.getName(), p.getPosition())))
);

But I'm getting a compilation error:

variable 'players' might not have been initialized

Why is this happening? Maybe there's an alternative way to create the stream, I was thinking of using flatMap but not sure how to apply it.


Solution

  • First of all, you need to understand that Streams don't act like Loops.

    Hence, don't try to mimic a loop. Examine the tools offered by the API. Operation forEach() is there for special cases when you need to perform side-effects, not in order to accumulate elements from the stream into a Collection.

    Note: with teams.forEach() you're not actually using a stream, but method Iterable.forEach() which is available with every implementation of Iterable.

    To perform reduction on streams, we have several specialized operations like collect, reduce, etc. (for more information refer to the API documentation - Reduction).

    collect() operation is meant to perform mutable reduction. You can use to collect the data into a list by providing built-in Collector Collectors.toList() as an argument. And since Java 16 operation toList() was introduced into API, which is implemented on top of the toArray() operation and performs better than namesake collector (therefore it's a preferred option if your JDK version allows you to use it).

    I was thinking of using flatMap but not sure how to apply it.

    Operation flatMap() is meant to perform one-to-many transformations. It expects a Function which takes a stream element and generates a Stream of the resulting type, elements of the generated stream become a replacement for the initial element.

    Note: that general approach to writing streams to use as fewer operations as possible (because one of the main advantages that Functional programming brings to Java is simplicity). For that reason, applying flatMap() when a stream element produces a Collection in a single step is idiomatic, since it's sorter than performing map().flatMap() in two steps.

    That's how implementation might look like:

    List<Team> teams = List.of();
    
    List<Player> players = teams.stream()                // Stream<Team>
        .flatMap(team -> team.getTeamPlayers().stream()) // Stream<Player>
        .map(player -> new Player(player.getName(), player.getPosition()))
        .toList(); // for Java 16+ or collect(Collectors.toList())