[英]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
对象的列表,我想按a
, b
和c
分组并生成一张地图。 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
. 由于未实现按多个字段分组,因此必须使用由a
, b
和c
的值组成的复合键。 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
与聚合域密钥a
, b
和c
从Foo
类和List<Integer>
收集所有值d
的字段值。在下面的例子我已经创建MapKey
类-即聚合的那些字段一个辅助类并实现hashCode
和equals
方法,因此可以将其用作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. 我还使Foo
, FooResult
和MapKey
不可变-当您必须处理流转换时,这始终是一个不错的选择。 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.