Java 8按多个字段分组为单个映射

时间:2017-07-27 20:08:48

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

我有一个这样的课程:

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<String, List<Foo>>。我有什么想法吗?

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

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

2 个答案:

答案 0 :(得分:2)

由于未实现多个字段的组,您必须使用由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;
    }
));

答案 1 :(得分:1)

您还可以将中间Press Command+Space and type Terminal and press enter/return key. Run in Terminal app: ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null and press enter/return key. Wait for the command to finish. Run: brew install mailutils 与来自Map类和a值的字段bcFoo汇总的密钥一起使用收集所有List<Integer>字段值。在下面的示例中,我创建了d类 - 一个帮助程序类,它聚合这些字段并实现MapKeyhashCode方法,因此它可以是用作equals中的键。

HashMap

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

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

我还提出了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]} FooFooResult不可变 - 当你必须处理流转换时,这总是一个不错的选择。您不希望在流操作期间产生任何副作用,并且不可变对象保证这一点。