Convert to a map using Java 8 and lambdas

I have

List<Gift> gifts = new ArrayList<>();
gifts .add(new Gift().withType(INF, CHD));
gifts .add(new Gift().withType(ADT, CHD));
gifts .add(new Gift().withType(INF, ADT));

Gift has a method List<Type> getTypes();

and now I want to convert the gift list to something like Map<Type,List<Gift>>. I would like to do this with Java 8 and lambdas on the same line. Is it possible?

public class Gift {

    public List<Type> getTypes() {
        return types;
    }

    public Gift withType(Type... types) {
        this.types = Arrays.asList(types);
        return this;
    }

    List<Type> types = new ArrayList<>();
}

public enum Type {
    ADT,
    CHD,
    INF;
}

Previous old code (it looks awful). That's all I have.

Map<Type, List<Gift>> byTypes = new HashMap<>();

for (Gift gift : gifts) {
    for (Type type : gift.getTypes()) {
        List<Gift> giftList = byTypes.get(type);
        if (giftList == null) {
            giftList = new ArrayList<>();
        }
        giftList.add(gift);
        byTypes.put(type,giftList);
    }
}
+4
source share
4 answers

Using Guava Multimap :

ListMultimap<Type, Gift> multimap = ArrayListMultimap.create();

gifts.forEach(g -> g.getTypes().forEach(t -> multimap.put(t, g)));

Map<Type, Collection<Gift>> map = multimap.asMap();
+3
source

Ok, I found a solution that satiated me :-) I wrote my collector: -D

Map<Type, List<Gift>> collect1 = gifts.stream().collect(new TypeToManyGiftCollector());

public class TypeToManyGiftCollector
        implements Collector<Gift, Map<Type, List<Gift>>, Map<Type, List<Gift>>> {

    @Override
    public Supplier<Map<Type, List<Gift>>> supplier() {
        return () -> new HashMap<Type, List<Gift>>() {{
            for (Type type : Type.values()) {
                put(type, new ArrayList<Gift>());
            }
        }};
    }

    @Override
    public BiConsumer<Map<Type, List<Gift>>, Gift> accumulator() {
        return (Map<Type, List<Gift>> map, Gift gift) -> {
            gift.getTypes().stream().forEach(type -> map.get(type).add(gift));
        };
    }

    @Override
    public BinaryOperator<Map<Type, List<Gift>>> combiner() {
        return (Map<Type, List<Gift>> map1, Map<Type, List<Gift>> map2) ->
        {
            for (Type type : Type.values()) {
                map1.get(type).addAll(map2.get(type));
            }
            return map1;
        };
    }

    @Override
    public Function<Map<Type, List<Gift>>, Map<Type, List<Gift>>> finisher() {
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH));
    }
}
+1
source

Pair P, . , , Java , .

package play;

import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static play.Type.*;

public class Play {
  static class P<K,V> { K k; V v; P(K kk,V vv) {k=kk;v=vv;}}
  public static void main(String[] args) throws IOException {
    List<Gift> gifts = new ArrayList<>();
    gifts .add(new Gift().withType(INF, CHD));
    gifts .add(new Gift().withType(ADT, CHD));
    gifts .add(new Gift().withType(INF, ADT));
    Map<Type,List<Gift>> m = gifts.stream()
      .flatMap((g)->g.getTypes().stream().map((t)->new P<>(t,g)))
      .collect(
        Collectors.groupingBy(
          (p)->p.k,
          // Does not work with <code>new EnumMap<>(Type.class)</code>
          // I dont know why, ...
          ()->new EnumMap(Type.class),
          Collectors.mapping(
            (p)->p.v, 
            Collectors.toList() 
          )
        )
      );
    System.out.println("Map: "+m.toString());
  }
}

My only problem in this code is that I cannot explain the need for an "Untyped" Map as a factory map. Feel free to explain if you know why ...

0
source

I found another solution with a reduction method. In my opinion, less and easier to understand.

      HashMap<Type, List<Gift>> result = gifts.stream().reduce(
            new HashMap<Type, List<Gift>>() {{
                asList(Type.values()).stream().forEach(type -> put(type, new ArrayList<>()));
            }}
            ,
            (map, gift) -> {
                gift.getTypes().stream().forEach(type -> map.get(type).add(gift));
                return map;
            }
            ,
            (map1, map2) -> {
                asList(Type.values()).stream().forEach(type -> map1.get(type).addAll(map2.get(type)));
                return map1;
            }
    );
0
source

All Articles