简体   繁体   English

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

[英]Java 8 Grouping by Multiple Fields into Single Map

I have a class like this: 我有这样的课:

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

I have a list of Foo objects that I want to group by a , b , and c and produce a map. 我有一个Foo对象的列表,我想按abc分组并生成一张地图。 Here's what I have so far: 这是我到目前为止的内容:

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

But that itself is wrong. 但这本身是错误的。 I don't know how to groupby multiple fields but still produce a Map<String, List<Foo>> . 我不知道如何对多个字段进行分组,但是仍然会生成Map<String, List<Foo>> Any ideas what I'm doing wrong? 有什么想法我做错了吗?

Edit 1: If I have the following Foo's: 编辑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}

Then it should group to: 然后应归类为:

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

My original post was misleading in what exactly I wanted but basically it needs to group by a, b, d and produce a list of d. 我的原始帖子在我想要的内容上产生了误导,但基本上它需要按a,b,d分组并生成d列表。 I know I'll probably have to create a new class to store them in like so: 我知道我可能必须创建一个新类来像这样存储它们:

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

How can I group and map to a new class like shown above? 我如何分组和映射到一个新的类,如上所示?

As a group by multiple fields is not implemented you have to use a composite key consisting of values from a , b and c . 由于未实现按多个字段分组,因此必须使用由abc的值组成的复合键。 With that key the collect operation can be used like this with the Collector#of() factory method. 使用该键,可以通过Collector#of()工厂方法像这样使用collect操作。

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

You can also use intermediate Map with a key that aggregates fields a , b and c from Foo class and List<Integer> value that collects all d field values.. In below example I have created MapKey class - a helper class that aggregates those fields and implements hashCode and equals methods so it can be used as a key in a HashMap . 也可以使用中间Map与聚合域密钥abcFoo类和List<Integer>收集所有值d的字段值。在下面的例子我已经创建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;
        }
    }
}

Then as you can see you can do your transformation is 6 lines of code. 然后如您所见,您可以进行6行代码的转换。 The output of this program is following: 该程序的输出如下:

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

I've also made Foo , FooResult and MapKey immutable - this is always a good choice when you have to deal with stream transformations. 我还使FooFooResultMapKey不可变-当您必须处理流转换时,这始终是一个不错的选择。 You don't want to have any side effects during stream manipulation and immutable objects guarantee that. 您不希望在流操作期间产生任何副作用,并且不可变的对象可以保证这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM