我有一个Foo类
class Foo {
private String id;
private String currency;
private BigDecima amount;
... constructor, getters & setters
现在有一个List集合中存有Foo类中的字段。
现在需要相同的id和currency上的amount相加。汇总成一个新的List集合。
最好使用lambda表达式
我有一个Foo类
class Foo {
private String id;
private String currency;
private BigDecima amount;
... constructor, getters & setters
现在有一个List集合中存有Foo类中的字段。
现在需要相同的id和currency上的amount相加。汇总成一个新的List集合。
最好使用lambda表达式
既然题主提到说要用lambda
表达式,而lambda
本质来说就是一种写法,用stream
的内部循环和传统的外部循环来比较,那就是写法不一样,写法不一样那就更应该明确当前需要的输入和输出了,输入是给清楚了,但是输出呢?题主只是说要返回一个新的List
集合,这个List
里装的啥?不同的东西最终可能导致写法的不太一致,没准思考方式也不太一样哈
我暂且以最终还是返回List<Foo>
为例,做个参考。
如果你想直接看最后的答案,可以直接拖到最后,下面只是我的一个思考过程而已
因为这里需要处理的主要问题就是相同的id
和currency
上的amount
相加,那归根到底就是相同的id
和currency
的Foo
对象要放在一起处理,这就是归类,归类在stream
的api
当然就要用Collectors.groupBy
方法了
Collectors.groupBy
有三个重载方法
groupingBy(Function classifier)
groupingBy(Function classifier, Collector downstream)
groupingBy(Function classifier, Supplier mapFactory, Collector downstream)
我把泛型去掉方便好看,其实参数也就是三种
classifier
:根据什么方式去做分类,这是一个Function
mapFactory
:最终分类的数据以什么类型map
存放,因为分类嘛,就是相同类型的对象在一起,所以最终肯定是一个map
结构downstream
:同一类的对象应该什么方式去做收集或者叫聚合为了方便理解这三个参数,我们如果直接使用Collectors.groupingBy(Foo::getId)
最后得到的就是Map<String, List<Foo>>
List<Foo> fooList = new ArrayList<>();
Map<String, List<Foo>> map = fooList.stream().collect(Collectors.groupingBy(Foo::getId));
我们可以看一下一个参数的Collectors.groupingBy
是怎么实现的
它调用的是两个参数的方法,并且给downstream
参数传递的是常用的toList()
方法
这两个参数的方法又是调用的三个参数的方法
所以好理解为啥是最终得到Map<String, List<Foo>>
,有了这个准备的基础我们再来看题主的问题
需要按照id
和currency
分类, 那我们就创建一个分类的方式
fooList.stream()
.collect(Collectors.groupingBy(foo -> foo.getId() + "_" + foo.getCurrency()))
这样写不太好看,我们可以把这个分类方式放在Foo
里
@Getter
@Setter
@AllArgsConstructor
public class Foo {
private String id;
private String currency;
private BigDecimal amount;
public String groupKey() {
return this.id + "_" + this.currency;
}
}
那我们就可以按照方法引用的方式写成
Map<String, List<Foo>> map = fooList.stream()
.collect(Collectors.groupingBy(Foo::groupKey));
接下来我们考虑如何合并这些同类Foo
对象的amount
值,由于我们本身来说最终的结果不需要对于Collectors.groupingBy
方法的第二个参数mapFactory
做过多定制,所以我们就只采用两个参数的方法了
剩下这个参数downstream
需要去处理amount
的问题,相当于我们现在已经按照Foo::groupKey
的方式做了归类,我们拿到了同一类的一堆Foo
对象
downstream
参数写Collectors.toList
,那最终结果肯定是Map<String, List<Foo>>
downstream
参数写Collectors.toSet
,那最终结果肯定是Map<String, Set<Foo>>
但是这些方式不是我们想要的,我们不想要把这一堆的Foo
对象转换为Set
或者List
,我们是希望把他们合并在一起,也就是
一堆Foo -> 一个Foo
那对应stream
的api
当然就要用Collectors.reducing
方法了,这个reducing
就是聚合的方法,把多个聚合成一个,正是我们想要的
reducing
方法也有三个参数,不过比较稍微复杂一点点,我之前也有个回答提到了java8中3个参数的reduce方法怎么理解? 你可以去看一下,虽然里面说的是stream.reduce
,但是和Collectors.reducing
是一样的
总得来说就是处理两个相同的Foo
如何合并为一个Foo
,也就是提供reducing
方法参数里的一种处理方式BinaryOperator
当然我们也可以把这种处理方式写到Foo
类中,新增merge
方法
@Getter
@Setter
@AllArgsConstructor
public class Foo {
private String id;
private String currency;
private BigDecimal amount;
public String groupKey() {
return this.id + "_" + this.currency;
}
public Foo merge(Foo foo) {
return new Foo(this.id, this.currency, this.amount.add(foo.getAmount()));
}
}
结合之前的groupKey
的写法,现在我们这个分类代码就可以写为
fooList.stream()
.collect(Collectors.groupingBy(Foo::groupKey, Collectors.reducing(Foo::merge)));
看似现在可以了,其实还不行,因为此时返回的map的格式其实是Map<String, Optional<Foo>>
为啥map
的value
是Optional
呢,其实主要就是因为我们使用了reducing
的一个参数的方法,只是指明了如何合并多个对象,但是万一这个stream
是空的,所以reducing
对应一个参数的方法返回的就是Optional
了
但是我们是先分类,然后再相同类中做reducing
,所以只要有分类,reducing
一定不会为空,所以这个Optional
一定是有值的,那么我们是可以直接get
出来的,不需要Optional
的保护
相同于我们做完了reducing
操作之后,还需要再做一个get
操作,那这个对应stream
的api
当然就要用Collectors.collectingAndThen
方法了
从方法名就可以看得出来,这个方法就是先收集然后再做其他操作,跟我们的需求就是吻合的,看方法签名
collectingAndThen(Collector downstream, Function finisher)
也是两个参数,第一个参数就是如何收集Collector
,第二个就是转换的Function
根据我们之间的思考,第一个Collector
参数就是Collectors.reducing(Foo::merge)
,那第二个参数Function
显然也是我们之前说的get
,也就是Optional::get
于是我们分类的代码就可以变为
fooList.stream()
.collect(Collectors.groupingBy(
Foo::groupKey,
Collectors.collectingAndThen(
Collectors.reducing(Foo::merge),
Optional::get)))
此时最终返回的就是Map<String, Foo>
,因为最终我们要的只是里面的values
,所以我们再加上map
的values()
方法即可
Collection<Foo> values = fooList.stream()
.collect(Collectors.groupingBy(
Foo::groupKey,
Collectors.collectingAndThen(
Collectors.reducing(Foo::merge),
Optional::get)))
.values();
但是注意此时返回的是Collection
,不是List
,如果题主不纠结这个的话,这样就可以了,但是非要转换为List
只有不用values()
方法而是再次循环一次Map<String, Foo>
List<Foo> values = fooList.stream()
.collect(Collectors.groupingBy(
Foo::groupKey,
Collectors.collectingAndThen(
Collectors.reducing(Foo::merge),
Optional::get)))
.entrySet()
.stream()
.map(Map.Entry::getValue)
.collect(Collectors.toList());
基础Foo
类
@Getter
@Setter
@AllArgsConstructor
public class Foo {
private String id;
private String currency;
private BigDecimal amount;
public String groupKey() {
return this.id + "_" + this.currency;
}
public Foo merge(Foo foo) {
return new Foo(this.id, this.currency, this.amount.add(foo.getAmount()));
}
}
groupingBy
+collectingAndThen
+reducing
)List<Foo> values = fooList.stream()
.collect(Collectors.groupingBy(
Foo::groupKey,
Collectors.collectingAndThen(
Collectors.reducing(Foo::merge),
Optional::get)))
.entrySet()
.stream()
.map(Map.Entry::getValue)
.collect(Collectors.toList());
Collector
)因为上一个方案各种groupingBy
+collectingAndThen
+reducing
其实他们返回的都是一个Collector
,由于jdk
给的Collector
实现不一定完全满足我们的需求,所以不用jdk
自带的,我们定制一个Collector也是可以的,之前也小写了一个文章Java8 collector接口的定制实现介绍,可以去看看,看完了再理解我写的,你会发现其实这种方案也挺简单的
public class FooCollectorImpl implements Collector<Foo, List<Foo>, List<Foo>> {
private Map<String, Integer> map = new HashMap<>();
@Override
public Supplier<List<Foo>> supplier() {
return ArrayList::new;
}
@Override
public BiConsumer<List<Foo>, Foo> accumulator() {
return (fooList, foo) -> {
String key = foo.groupKey();
Integer index = map.get(key);
if (index == null) {
fooList.add(foo);
map.put(key, fooList.size() - 1);
}else {
Foo innerFoo = fooList.get(index);
innerFoo = innerFoo.merge(foo);
fooList.add(index, innerFoo);
}
};
}
@Override
public BinaryOperator<List<Foo>> combiner() {
return (fooList, fooList2) -> fooList;
}
@Override
public Function<List<Foo>, List<Foo>> finisher() {
return Function.identity();
}
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
}
}
因为定制的Collector
已经把很多逻辑实现了,所以调用的时候显得非常简单
List<Foo> values = fooList.stream().collect(new FooCollectorImpl());
以上,仅供参考~╭( ̄▽ ̄)╯
3 回答2.2k 阅读✓ 已解决
3 回答3.7k 阅读✓ 已解决
8 回答2.9k 阅读
4 回答2.3k 阅读✓ 已解决
3 回答2.2k 阅读✓ 已解决
1 回答1.9k 阅读✓ 已解决
3 回答1.5k 阅读✓ 已解决
为什么非要
lambda
?