Java流:添加到地图但避免变异

时间:2017-12-07 08:21:39

标签: java dictionary java-8 grouping java-stream

我经常发现自己处于需要从Set或Map创建List个对象的情况。 键通常是一些String或Enum等,值是一些新数据集中在一起的新对象。 对我而言,通常的做法是首先创建Map<String, SomeKeyValueObject>,然后迭代我进入的Set或List并改变我新创建的地图。

如以下示例所示:

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;
   }
}

这感觉非常不洁净。我们创建一个地图,然后一遍又一遍地改变它。

我喜欢java 8s使用Stream并创建非变异数据结构(或者更确切地说,你没有看到变异)。那么有没有办法将这种数据分组转化为使用声明式方法而不是命令式方法?

我试图在https://stackoverflow.com/a/34453814/3478016中实施这个建议,但我似乎有些磕磕绊绊。使用答案中的方法(使用Collectors.groupingByCollectors.mapping的建议),我可以将数据分类到地图中。但是我无法对数据进行分组&#34;进入同一个对象。

有没有办法以声明的方式做到这一点,还是我坚持要求?

3 个答案:

答案 0 :(得分:5)

您可以使用合并功能Collectors.toMap代替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;
                                  }));

必须使GroupedDataObject构造函数可访问才能使其生效。

答案 1 :(得分:2)

如果您避开GroupedDataObject而只是想要一张带有密钥和列表的地图,则可以使用您一直在查看的Collectors.groupingBy

Collectors.groupingBy允许你这样做:

List<SomeObject> list = getSomeList();

Map<SomeKey, List<SomeObject>> = list.stream().collect(Collectors.groupingBy(SomeObject::getKeyMethod));

这将要求SomeKey正确实施equalshashValue

答案 2 :(得分:2)

有时候流不是可行的方式。我相信这是其中一次。

使用merge()进行一些重构可以为您提供:

Map<String, MyTuple> groupData(final List<SomeData> list) {
    Map<String, MyTuple> map = new HashMap<>();
    list.forEach(d -> map.merge(d.valueToGroupBy(), new MyTuple(data.displayName(), data.data()), 
      (a, b) -> {a.addAll(b.getDatas()); return a;});

假设有合理的课程来保存你的东西:

class MyTuple {
    String displayName;
    List<Object> datas = new ArrayList<>();
    // getters plus constructor that takes 1 data and adds it to list
}