Java streams: Add to map but avoid mutation

Chewtoy :

I often find myself in a situation where I need to create a Map of objects from a Set or List. The key is usually some String or Enum or the like, and the value is some new object with data lumped together. The usual way of doing this, for my part, is by first creating the Map<String, SomeKeyValueObject> and then iterating over the Set or List I get in and mutate my newly created map.

Like the following example:

class Example {
   Map<String, GroupedDataObject> groupData(final List<SomeData> list){
      final Map<String, GroupedDataObject> map = new HashMap<>();
      for(final SomeData data : list){
         final String key = data.valueToGroupBy();
         map.put(key, GroupedDataObject.of(map.get(key), data.displayName(), data.data()));
      }
      return map;
   }

}

class SomeData {
   private final String valueToGroupBy;
   private final Object data;
   private final String displayName;

   public SomeData(final String valueToGroupBy, final String displayName, final Object data) {
      this.valueToGroupBy = valueToGroupBy;
      this.data = data;
      this.displayName = displayName;
   }

   public String valueToGroupBy() {
      return valueToGroupBy;
   }

   public Object data() {
      return data;
   }

   public String displayName() {
      return displayName;
   }
}

class GroupedDataObject{

   private final String key;
   private final List<Object> datas;

   private GroupedDataObject(final String key, final List<Object> list) {
      this.key = key;
      this.datas = list;
   }

   public static GroupedDataObject of(final GroupedDataObject groupedDataObject, final String key, final Object data) {
      final List<Object> list = new ArrayList<>();
      if(groupedDataObject != null){
         list.addAll(groupedDataObject.datas());
      }
      list.add(data);
      return new GroupedDataObject(key, list);
   }

   public String key() {
      return key;
   }

   public List<Object> datas() {
      return datas;
   }
}

This feels very unclean. We create a map, and then mutate it over and over.

I've taken a liking to java 8s use of Streams and creating non-mutating data structures (or rather, you don't see the mutation). So is there a way to turn this grouping of data into something that uses a declarative approach rather than the imperative way?

I tried to implement the suggestion in https://stackoverflow.com/a/34453814/3478016 but I seem to be stumbling. Using the approach in the answer (the suggestion of using Collectors.groupingBy and Collectors.mapping) I'm able to get the data sorted into a map. But I can't group the "datas" into one and the same object.

Is there some way to do it in a declarative way, or am I stuck with the imperative?

Eran :

You can use Collectors.toMap with a merge function instead of Collectors.groupingBy.

Map<String, GroupedDataObject> map =
    list.stream()
        .collect(Collectors.toMap(SomeData::valueToGroupBy,
                                  d -> {
                                     List<Object> l = new ArrayList<>();
                                     l.add(d.data());
                                     return new GroupedDataObject(d.valueToGroupBy(), l);
                                  },
                                  (g1,g2) -> {
                                      g1.datas().addAll(g2.datas());
                                      return g1;
                                  }));

The GroupedDataObject constructor must be made accessible in order for this to work.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=475564&siteId=1