How to groupBy object properties and map to another object using Java 8 Streams?

  • A+

Suppose I have a group of bumper cars, which have a size, a color and an identifier ("car code") on their sides.

class BumperCar {     int size;     String color;     String carCode; } 

Now I need to map the bumper cars to a List of DistGroup objects, which each contains the properties size, color and a List of car codes.

class DistGroup {     int size;     Color color;     List<String> carCodes;      void addCarCodes(List<String> carCodes) {         this.carCodes.addAll(carCodes);     } } 

For example,

[     BumperCar(size=3, color=yellow, carCode=Q4M),     BumperCar(size=3, color=yellow, carCode=T5A),     BumperCar(size=3, color=red, carCode=6NR) ] 

should result in:

[     DistGroup(size=3, color=yellow, carCodes=[ Q4M, T5A ]),     DistGroup(size=3, color=red, carCodes=[ 6NR ]) ] 

I tried the following, which actually does what I want it to do. But the problem is that it materializes the immediate result (into a Map) and I also think that it can be done at once (perhaps using mapping or collectingAndThen or reducing or something), resulting in more elegant code.

List<BumperCar> bumperCars = ... Map<SizeColorCombination, List<BumperCar>> map =     .collect(groupingBy(t -> new SizeColorCombination(t.getSize(), t.getColor())));  List<DistGroup> distGroups = map.entrySet().stream()     .map(t -> {         DistGroup d = new DistGroup(t.getKey().getSize(), t.getKey().getColor());         d.addCarCodes(t.getValue().stream()             .map(BumperCar::getCarCode)             .collect(toList()));         return d;     })     .collect(toList()); 

How can I get the desired result without using a variable for an immediate result?

Edit: How can I get the desired result without materializing the immediate result? I am merely looking for a way which does not materialize the immediate result, at least not on the surface. That means that I prefer not to use something like this:     .collect(...) // Materializing     .stream()     .collect(...); // Materializing second time 

Of course, if this is possible.

Note that I omitted getters and constructors for brevity. You may also assume that equals and hashCode methods are properly implemented. Also note that I'm using the SizeColorCombination which I use as group-by key. This class obviously contains the properties size and color. Tuple or Pair may also be used.
Edit: Also note that an ol' skool for loop can be used instead or course, but that is not in the scope of this question.


If we assume that DistGroup has hashCode/equals based on size and color, you could do it like this:

bumperCars     .stream()     .map(x -> {         List<String> list = new ArrayList<>();         list.add(x.getCarCode());         return new SimpleEntry<>(x, list);     })     .map(x -> new DistGroup(x.getKey().getSize(), x.getKey().getColor(), x.getValue()))     .collect(Collectors.toMap(         Function.identity(),         Function.identity(),         (left, right) -> {             left.getCarCodes().addAll(right.getCarCodes());             return left;         }))     .values(); // Collection<DistGroup> 


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: