Oceanic
Oceanic

Reputation: 1458

Java 8 streams - modifying all elements in a group

I have a collection of objects of Class A

class A {
    String code;
    long timestamp;
    long largestTimestamp;
}

I have to populate the largestTimestamp field for each object (the largest "timestamp" value in the group of objects with the same code). I can do this in two steps as follows -

Map<String, Long> largestTimestampMap = list.stream().collect(Collectors.toMap(A::getCode, A::getTimestamp, Long::max));
list.forEach(a -> a.setLargestTimestamp(largestTimestampMap.get(a.getCode())));

Is there a way to combine these into a single stream chain?

Upvotes: 5

Views: 567

Answers (3)

Yashodeep Vaidya
Yashodeep Vaidya

Reputation: 11

Just a different thought, but can't you structure your class A like as shown below

class A {
  String code;
  long timeStamp;
  TimeStampWrapper timeStampWrapper;
}

class TimeStampWrapper {
  long maxTimeStamp;
  long minTimeStamp;
  ...
}

With this approach you will only need 1 stream traversal and just a single set operation to set maxTimeStamp, minTimeStamp, etc etc.

Upvotes: 0

Ousmane D.
Ousmane D.

Reputation: 56423

Yes, you can combine them into a single pipeline as follows:

list.stream()
     .map(a -> new A(a.getCode(), a.getTimestamp(),
                list.stream()
                    .filter(b -> a.getCode().equals(b.getCode()))
                    .mapToLong(A::getTimestamp)
                    .max().getAsLong()))           
     .collect(Collectors.toList());

or if you want to avoid creating a new list but instead modify the existing one like in your example then use replaceAll:

list.replaceAll(a -> new A(a.getCode(), a.getTimestamp(),
                        list.stream()
                                .filter(b -> a.getCode().equals(b.getCode()))
                                .mapToLong(A::getTimestamp)
                                .max().getAsLong()));

However, I'd recommend avoiding this approach:

1) because it has worse performance than your current implementation, this solution requires iterating over list again forEach element in it. Whereas the Map approach you've shown only requires a single get call and we all know how fast looking up in a map is.

2) There's more code.

Yes, you can refactor the code inside the map intermediate operation into a method to make it look shorter but ultimately it's still longer.

Sometimes the best approach requires two or more separate lines and in this case, it's best to proceed with your approach.

Upvotes: 4

Naman
Naman

Reputation: 31868

You might just be looking for (if populate the largestTimestamp field for each object means saving the same largestTimeStamp for the entire list) :

// find the largest timestamp
long largestTimeStamp = list.stream()
        .mapToLong(A::getTimestamp)
        .max()
        .orElse(Long.MIN_VALUE);

// iterate through the list to set its value
list.forEach(a -> a.setLargestTimestamp(largestTimeStamp));

If bound to do this under a single line of code, you can use (edited to align with the existing code as clarified by Aomine in comments) :

List<A> output = list.stream()
        .map(a -> new A(a.getCode(), a.getTimestamp(),
                list.stream().filter(b -> a.getCode().equals(b.getCode()))
                        .mapToLong(A::getTimestamp).max().orElse(a.getTimestamp())))
        .collect(Collectors.toList());

Upvotes: 3

Related Questions