简体   繁体   中英

What is the best practice for managing Java 8 streams with multiple results

How to process forEach in Java 8 using streams when I want to have multiple results from the iteration. Of course calling List.add method in stream forEach operation is not an option... How would you rewrite this code using Java 8 streams?

additionalWebsitesList = Lists.newArrayList();
String additionalWebistesCsv = "";
for (String website : additionalWebsites) {
    WebsiteModel websiteModel = getWebsiteModel(website);

    //How this can be processed via Stream???
    additionalWebsitesList.add(websiteModel);
    areaModels.addAll(websiteModel.getAreas());
    additionalWebistesCsv += websiteModel.getId();
}

The way to do things like this with streams is to use a custom collector. Suppose we define the following class:

public class Results {
    private final List<WebsiteModel> additionalWebsitesList = new ArrayList<>();
    private final List<AreaModel> areaModels = new ArrayList<>();
    private final StringBuilder additionalWebsitesCsv = new StringBuilder();

    public void accumulate(WebsiteModel websiteModel) {
        additionalWebsitesList.add(websiteModel);
        areaModels.addAll(websiteModel.getAreas());
        additionalWebsitesCsv.append(websiteModel.getId());
    }

    public void combine(Results another) {
        additionalWebsitesList.addAll(another.additionalWebsitesList);
        areaModels.addAll(another.areaModels);
        additionalWebsitesCsv.append(another.additionalWebsitesCsv);
    }

    public List<WebsiteModel> getAdditionalWebsitesList() {
        return additionalWebsitesList;
    }

    public List<AreaModel> getAreaModels() {
        return areaModels;
    }

    public String getAdditionalWebsitesCsv() {
        return additionalWebsitesCsv.toString();
    }
}

With this Results class in place, you're ready to collect the results:

Results results = additionalWebsites.stream()
    .map(this::getWebsiteModel)
    .collect(Results::new, Results::accumulate, Results::combine);

This uses the Stream.collect method. The first argument is a Supplier that creates the mutable object into which elements of the stream are to be accumulated via the accumulator provided in the second argument. The third argument is a merger that will only be used to combine partial results if the stream is parallel.

What is the best practice for managing Java 8 streams with multiple results

Of course calling List.add method in stream forEach operation is not an option.

The best practice is not forcing you to use streams as the use case is not appropriate.
The Stream interface javadoc describes itself as :

A sequence of elements supporting sequential and parallel aggregate operations.

Stream operations return themselves a sequence of typed elements.
So Stream collects will return finally a single kind of thing based on the type of the sequence of elements : unitary or a collection of.
We could for example collect a Foo , a List of Foo , a Map of Foo indexed by Integer keys, and so for...
Stream collects are not designed to collect different custom things such as a List of Foo + a List of Bar + the count of Bar + Foo .

As @Eugene underlined, Streams also provide a way to get IntSummaryStatistics that is a set of things : common aggregates that are minimum, maximum, sum, and average. But should we not consider that it collects finally a single thing : statistics ?

So you have three ways :

  • putting aside Stream for your use case and keeping the for loop.

  • using multiple streams : one stream by custom thing that you want to collect.

  • using a single stream with a custom collector.

I would not use the second way in any case as it will produce a less readable and straight code that your actual.

About the last way (custom collector) illustrated by the Federico Peralta Schaffner answer. It is straighter and it allows to benefit from stream parallelism. So it is an option to consider.
But it also requires more boiler plate code and has more reading indirection to understand the actual logic.
So I think that I would introduce a custom collector only in two cases :

  • the collector is reused.
  • we want to benefit from the stream parallelism and we have a very important number of elements to process in (Federico Peralta Schaffner explains it very well in its comment , thanks).

And in any other cases, I would keep the for loop.

That code as Java 8 streams:

additionalWebsitesList = additionalWebsites.stream()
        .map(this::getWebsiteModel)
        .collect(Collectors.toList());
areaModels = additionalWebsites.stream()
        .flatMap(w -> getWebsiteModel(w).getAreas().stream())
        .collect(Collectors.toList());
String additionalWebistesCsv = additionalWebsites.stream()
        .map(this::getWebsiteModel)
        .map(WebsiteModel::getId)
        .collect(Collectors.joining());

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