【问题标题】:Java 8 Grouping by Multiple Fields into Single MapJava 8 按多个字段分组到单个映射中
【发布时间】:2018-01-03 17:39:48
【问题描述】:

我有这样的课:

public class Foo {
    private String a;
    private String b;
    private LocalDate c;
    private int d;
}

我有一个Foo 对象列表,我想按abc 分组并生成一张地图。这是我目前所拥有的:

Map<String, List<Foo>> test = foos.stream().collect(Collectors.groupingBy(Foo::getA, Collectors.collectingAndThen(Collectors.groupingBy(Foo::getB), Collections.unmodifiableList())));

但这本身就是错误的。我不知道如何对多个字段进行分组,但仍会生成Map&lt;String, List&lt;Foo&gt;&gt;。任何想法我做错了什么?

编辑 1: 如果我有以下 Foo:

{"Test", "Test", "10/02/2015", 5}
{"Test", "Test", "10/02/2015", 4}
{"Test", "Test", "10/02/2015", 3}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}

那么它应该分组到:

{"Test", "Test", "10/02/2015", [5, 4, 3]}
{"Test", "Test", "2/02/2015", 5}
{"Test", "Potato", "2/02/2015", 5}

我的原始帖子在我想要的内容方面具有误导性,但基本上它需要按 a、b、d 分组并生成一个 d 列表。我知道我可能必须创建一个新类来像这样存储它们:

public class FooResult {
    private String a;
    private String b;
    private LocalDate c;
    private List<Integer> d;
}

如何分组和映射到如上所示的新类?

【问题讨论】:

  • 地图中的String-key 应该是什么? a?
  • 如果(唯一的)键是a,那么它们应该如何按bc分组?
  • 不能对 2 个字段执行 group-by 操作并期望返回 Map&lt;String, List&gt;
  • 您在帖子中的预期结果不是地图 - 字符串键不是唯一的。地图的键必须是包含 a、b 和 c 的复合类型。
  • 你的问题没有意义。您的预期输出显示 3 个键(每个条目),但您要求 1 个键。请解释一下。

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


【解决方案1】:

由于未实现由多个字段组成的分组,您必须使用由来自abc 的值组成的组合键。使用该键,收集操作可以像这样与Collector#of() 工厂方法一起使用。

Map<String, List<Integer>> result = foos.stream().collect(Collector.of(
    HashMap::new,
    ( map, foo ) -> {
        map.compute(foo.a + "_" + foo.b + "_" + foo.c, (key,list) -> {
            if(list == null){
                list = new ArrayList<>();
            }
            list.add(foo.d);
            return list;
        });
    },
    ( map1, map2 ) -> {
        map2.forEach(( k, v ) -> {
            map1.compute(k, (key, list) -> {
                if(list == null){
                    list = v;
                } else {
                    list.addAll(v);
                }
                return list;
            });
       });
       return map1;
    }
));

【讨论】:

  • 与其将compute 用于所有目的,不如将computeIfAbsent(key, x -&gt; new ArrayList&lt;&gt;()).add(value) 用于累加器,将merge(k,v, (l1,l2)-&gt;{l1.addAll(l2); return l1; }) 用于组合器更简单。但请注意,您只是在重新发明groupingBy 收集器; groupingBy(foo -&gt; foo.a + "_" + foo.b + "_" + foo.c) 正是这样做的。
【解决方案2】:

您还可以使用中间 Map 和一个键,该键聚合来自 Foo 类的字段 abc 以及收集所有 d 字段值的 List&lt;Integer&gt; 值。在下面例如,我创建了 MapKey 类 - 一个聚合这些字段并实现 hashCodeequals 方法的辅助类,因此它可以用作 HashMap 中的键。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class FooMain {

    public static void main(String[] args) {
        final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy");

        final List<Foo> foos = Arrays.asList(
                new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 5),
                new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 4),
                new Foo("Test", "Test", LocalDate.parse("10/02/2015", dateFormat), 3),
                new Foo("Test", "Test", LocalDate.parse("02/02/2015", dateFormat), 5),
                new Foo("Test", "Potato", LocalDate.parse("02/02/2015", dateFormat), 5)
        );

        List<FooResult> result = foos.stream()
                .collect(Collectors.groupingBy(foo -> new MapKey(foo.a, foo.b, foo.c), Collectors.mapping(Foo::getD, Collectors.toList())))
                .entrySet()
                .stream()
                .map(entry -> new FooResult(entry.getKey().a, entry.getKey().b, entry.getKey().c, entry.getValue()))
                .collect(Collectors.toList());

        result.forEach(System.out::println);
    }

    public static final class Foo {
        private final String a;
        private final String b;
        private final LocalDate c;
        private final int d;

        Foo(String a, String b, LocalDate c, int d) {
            this.a = a;
            this.b = b;
            this.c = c;
            this.d = d;
        }

        int getD() {
            return d;
        }
    }

    public static final class FooResult {
        private final String a;
        private final String b;
        private final LocalDate c;
        private final List<Integer> d;

        FooResult(String a, String b, LocalDate c, List<Integer> d) {
            this.a = a;
            this.b = b;
            this.c = c;
            this.d = d;
        }

        @Override
        public String toString() {
            return "FooResult{" +
                    "a='" + a + '\'' +
                    ", b='" + b + '\'' +
                    ", c=" + c +
                    ", d=" + d +
                    '}';
        }
    }

    public static final class MapKey {
        private final String a;
        private final String b;
        private final LocalDate c;

        MapKey(String a, String b, LocalDate c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof MapKey)) return false;

            MapKey mapKey = (MapKey) o;

            if (a != null ? !a.equals(mapKey.a) : mapKey.a != null) return false;
            if (b != null ? !b.equals(mapKey.b) : mapKey.b != null) return false;
            return c != null ? c.equals(mapKey.c) : mapKey.c == null;
        }

        @Override
        public int hashCode() {
            int result = a != null ? a.hashCode() : 0;
            result = 31 * result + (b != null ? b.hashCode() : 0);
            result = 31 * result + (c != null ? c.hashCode() : 0);
            return result;
        }
    }
}

然后你可以看到你可以做你的转换是 6 行代码。该程序的输出如下:

FooResult{a='Test', b='Potato', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-02-02, d=[5]}
FooResult{a='Test', b='Test', c=2015-10-02, d=[5, 4, 3]}

我还使FooFooResultMapKey 不可变——当您必须处理流转换时,这始终是一个不错的选择。您不希望在流操作期间产生任何副作用,不可变对象可以保证这一点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多