【问题标题】:Java Streams: group a List into a Map of MapsJava Streams:将列表分组为地图的地图
【发布时间】:2015-10-21 07:57:44
【问题描述】:

如何使用 Java Streams 执行以下操作?

假设我有以下课程:

class Foo {
    Bar b;
}

class Bar {
    String id;
    String date;
}

我有一个List<Foo>,我想将其转换为Map <Foo.b.id, Map<Foo.b.date, Foo>。即:首先按Foo.b.id 分组,然后按Foo.b.date

我正在努力使用以下两步方法,但第二步甚至无法编译:

Map<String, List<Foo>> groupById =
        myList
                .stream()
                .collect(
                        Collectors.groupingBy(
                                foo -> foo.getBar().getId()
                        )
                );

Map<String, Map<String, Foo>> output = groupById.entrySet()
        .stream()
        .map(
                entry -> entry.getKey(),
                entry -> entry.getValue()
                        .stream()
                        .collect(
                                Collectors.groupingBy(
                                        bar -> bar.getDate()
                                )
                        )
        );

提前致谢。

【问题讨论】:

  • 好吧,您确定列表中的每个元素都是唯一的吗?也就是说,一个 id 和一个日期将给出一个 Foo 对象?
  • 您想要Map &lt;Foo.b.id, Map&lt;Foo.b.date, Foo.b&gt; 还是Map &lt;Foo.b.id, Map&lt;Foo.b.date, List&lt;Foo.b&gt;&gt;
  • @Eran 你是对的,已编辑 :)

标签: java java-8 java-stream


【解决方案1】:

假设只有不同的Foo,您可以一次性对数据进行分组:

Map<String, Map<String, Foo>> map = list.stream()
        .collect(Collectors.groupingBy(f -> f.b.id, 
                 Collectors.toMap(f -> f.b.date, Function.identity())));

使用静态导入保存一些字符:

Map<String, Map<String, Foo>> map = list.stream()
        .collect(groupingBy(f -> f.b.id, toMap(f -> f.b.date, identity())));

【讨论】:

    【解决方案2】:

    假设(b.id, b.date) 对是不同的。如果是这样的话, 在第二步中,您不需要分组,只需收集到Map,其中键是foo.b.date,值是foo 本身:

    Map<String, Map<String, Foo>> map = 
           myList.stream()
                 .collect(Collectors.groupingBy(f -> f.b.id))    // map {Foo.b.id -> List<Foo>}
                 .entrySet().stream()
                 .collect(Collectors.toMap(e -> e.getKey(),                 // id
                                           e -> e.getValue().stream()       // stream of foos
                                                 .collect(Collectors.toMap(f -> f.b.date, 
                                                                           f -> f))));
    

    或者更简单:

    Map<String, Map<String, Foo>> map = 
           myList.stream()
                 .collect(Collectors.groupingBy(f -> f.b.id, 
                                                Collectors.toMap(f -> f.b.date, 
                                                                 f -> f)));
    

    【讨论】:

    • 感谢 Alex 提供代码中的 cmets。我没有意识到地图是在collect(Collectros.groupingBy()) 之后创建的。
    【解决方案3】:

    另一种方法是在您的密钥Bar 上支持平等契约:

    class Bar {
        String id;
        String date;
    
        public boolean equals(Object o){
           if (o == null) return false;
           if (!o.getClass().equals(getClass())) return false;
           Bar other = (Bar)o;
           return Objects.equals(o.id, id) && Objects.equals(o.date, date);
        }
    
        public int hashCode(){
           return id.hashCode*31 + date.hashCode;
        }    
    }
    

    现在你可以拥有一个Map&lt;Bar, Foo&gt;

    【讨论】:

    • OP 绝对希望 Bars 具有相同的 id 但不同的 date 也被存储,但在您的方法中它们将被丢弃。您的代码中的另一个缺陷是比较ids 的不安全方式——Objects.equals() 对此要好得多。而且我无法理解将o.datethis.id 进行比较的目的是什么
    • @SashaSalauyou 第一个陈述不正确,您可以使用此方法拥有相同的 id 和不同的日期。至于空值,Map 不允许空键,所以我假设它们没有。第三点是错字,可能也会影响第一点!
    • 是的,您的编辑基于iddate 而不是仅id。现在要搜索Foo,您需要创建虚拟Bar 对象——这当然可能是一种方法。 +1
    • @SashaSalauyou 是的,这是真的。但我更愿意使用地图而不是地图,除非我想执行部分查找,即查找所有匹配 id
    猜你喜欢
    • 2016-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-30
    相关资源
    最近更新 更多