TeemoSays234
TeemoSays234

Reputation: 3

Java Streams - Are you able to have multiple terminal operations (e.g. forEach)

I'm fairly new to Java and trying to learn how to use streams for easier code writing. If I can code like this:

Map<String, SomeConfig> temp = new HashMap<>();

resultStorage.forEach((key, value) -> key.getUsers().forEach(user -> {
    if (!temp.containsKey(user.getMeta())) {

        SomeConfig emailConfiguration = key
            .withCheck1(masterAccountId)
            .withCheck2(getClientTimezone())
            .withCheck3(user.getMeta());

        temp.put(user.getMeta(), emailConfiguration);
    }

    temp.get(user. getMeta()).getStreams().add(value);

}));

return new ArrayList<>(temp.values());

resultStorage declaration:

private Map< SomeConfig, byte[]> resultStorage = new ConcurrentHashMap<>();

getStreams is a getter on SomeConfig that returns a List<byte[]> as here:

    private List<byte[]> attachmentStreams = new ArrayList<>();
    public List<byte[]> getAttachmentStreams() {
        return attachmentStreams;
    }

My first attempt was something similar to this:

resultStorage.entrySet().stream()
    .forEach(entry -> entry.getKey().getUsers().forEach(user -> {
                            
    }));

Are we able to use a forEach within one of the streams terminating operation, forEach? How would a stream benefit in this case as I saw documentation that it can significantly improve readability and performance of older pre-Java8 code?

Edit: resultStorage holds a ConcurrentHashMap. It will contain Map<SomeConfig, byte[]> for email and attachments. Using another HashMap temp that is initially empty - we analyze resultStorage , see if temp contains a specific email key, and then put or add based on the existence of a user's email

Upvotes: 0

Views: 465

Answers (2)

Abra
Abra

Reputation: 20914

Notes after the code.

Map<String, SomeConfig> temp = resultStorage.keySet()
                                            .stream()
                                            .flatMap(key -> key.getUsers()
                                                               .stream()
                                                               .map(user -> new AbstractMap.SimpleEntry(user, key)))
                                            .collect(Collectors.toMap(e -> e.getKey().getMeta(),
                                                                      e -> e.getValue()
                                                                            .withCheck1(masterAccountId)
                                                                            .withCheck2(getClientTimezone())
                                                                            .withCheck3(e.getKey().getMeta())

resultStorage.keySet()

This returns Set<SomeConfig>.

stream()

This returns a stream where every element in the stream is an instance of SomeConfig.

.flatMap(key -> key.getUsers()
                   .stream()
                   .map(user -> new AbstractMap.SimpleEntry(user, key)))

Method flatMap() must return a Stream. The above code returns a Stream where every element is an instance of AbstractMap.SimpleEntry. The "entry" key is the user and the entry value is the key from resultStorage.

Finally I create a Map<String, SomeConfig> via [static] method toMap of class Collectors.

The first argument to method toMap is the key mapper, i.e. a method that extracts the [map] key from the AbstractMap.SimpleEntry. In your case this is the value returned by method getMeta() of the user – which is the key from AbstractMap.SimpleEntry, i.e. e.getKey() returns a user object.

The second argument to toMap is the value mapper. e.getValue() returns a SomeConfig object and the rest is your code, i.e. the withChecks.

There is no way I can test the above code because not only did you not post a minimal, reproducible example, you also did not post any sample data. Hence the above may be way off what you actually require.

Also note that the above code simply creates your Map<String, SomeConfig> temp. I could not understand the code in your question that processes that Map so I did not try to implement that part at all.

Upvotes: 0

Holger
Holger

Reputation: 298233

The terminal operation of entrySet().stream().forEach(…) is entirely unrelated to the getUsers().forEach(…) call within the Consumer. So there’s no problem of “multiple terminal operations” here.

However, replacing the Map operation forEach((key, value) -> … with an entrySet() .stream() .forEach(entry -> …) rarely adds a benefit. So far, you’re not only made the code longer, you introduced the necessity to deal with a Map.Entry instead of just using key and value.

But you can simplify your operation by using a single computeIfAbsent instead of containsKey, put, and get:

resultStorage.forEach((key, value) -> key.getUsers().forEach(user ->
    temp.computeIfAbsent(user.getMeta(), meta ->
        key.withCheck1(masterAccountId).withCheck2(getClientTimezone()).withCheck3(meta))
    .getStreams().add(value)));

Upvotes: 2

Related Questions