1

I have a List of Objects say List<Type1> that I have grouped using type.(using groupingBy) Now I want to convert that Map> into Type2 that has both the list and the Id of that group.

class Type1{
int id;
int type;
String name;
}

class Type2{
int type;
List<Type1> type1List;
}

This is what I have written to achieve this:

myCustomList
            .stream()
            .collect(groupingBy(Type1::getType))
            .entrySet()
            .stream()
            .map(type1Item -> new Type2() {
                {
                    setType(type1Item.getKey());
                    setType1List(type1Item.getValue());
                }
            })
            .collect(Collectors.toList());

This works perfectly. But I am trying to make the code even cleaner. Is there a way to avoid streaming this thing all over again and use some kind of flatmap to achieve this.

  • 1
    If you want to make the code even cleaner, stop creating a subclass of `Type2` (also known as the double-curly-brace anti-pattern). Give `Type2` a constructor accepting the type and type1list. Then, you can simply use `.map(t1 -> new Type2(t1.getKey(), t1.getValue()))`. – Holger Aug 23 '18 at 06:23
  • As @Holger commented, I think you don't need to `Type2` class and you can achieve to goal so simple. `Map> result = type1List.stream() .collect(Collectors.groupingBy(Type1::getType));` – Hadi J Aug 23 '18 at 06:53
  • 1
    @HadiJ my comment was more about the double-curly-brace initialization pattern, which creates a *subclass* of `Type2`. But yes, if `Type2` is only about these two properties, the `Map>` might already be sufficient for most purposes. – Holger Aug 23 '18 at 06:57

3 Answers3

2

You can pass a finisher function to the collectingAndThen to get the work done after the formation of the initial map.

List<Type2> result = myCustomList.stream()
    .collect(Collectors.collectingAndThen(Collectors.groupingBy(Type1::getType),
        m -> m.entrySet().stream()
            .map(e -> new Type2(e.getKey(), e.getValue()))
            .collect(Collectors.toList())));
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
1

You should give Type2 a constructor of the form

Type2(int type, List<Type1> type1List) {
    this.type = type;
    this.type1List = type1List;
}

Then, you can write .map(type1Item -> new Type2(type1Item.getKey(), type1Item.getValue())) instead of

.map(type1Item -> new Type2() {
    {
        setType(type1Item.getKey());
        setType1List(type1Item.getValue());
    }
})

See also What is Double Brace initialization in Java?
In short, this creates a memory leak, as it creates a subclass of Type2 which captures the type1Item its entire lifetime.

But you can perform the conversion as part of the downstream collector of the groupingBy. This implies that you have to make the toList explicit, to combine it via collectingAndThen with the subsequent mapping:

Collection<Type2> collect = myCustomList
    .stream()
    .collect(groupingBy(Type1::getType,
        collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))))
    .values();

If you really need a List, you can use

List<Type2> collect = myCustomList
    .stream()
    .collect(collectingAndThen(groupingBy(Type1::getType,
            collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))),
        m -> new ArrayList<>(m.values())));
Holger
  • 285,553
  • 42
  • 434
  • 765
0

You can do as mentioned below:

type1.map( type1Item -> new Type2(
     type1Item.getKey(), type1Item
)).collect(Collectors.toList());
Cœur
  • 37,241
  • 25
  • 195
  • 267