hai*_*isi 37 java lambda java-8 java-stream
我有一个Foo包含这些字段的课程:
id:int/name; String/targetCost:BigDecimal/actualCost:BigDecimal
我得到了这个类的对象的arraylist.例如:
new Foo(1, "P1", 300, 400),
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 30, 20),
new Foo(3, "P3", 70, 20),
new Foo(1, "P1", 360, 40),
new Foo(4, "P4", 320, 200),
new Foo(4, "P4", 500, 900)
Run Code Online (Sandbox Code Playgroud)
我想通过创建"targetCost"和"actualCost"的总和并将"行"分组来转换这些值,例如
new Foo(1, "P1", 660, 440),
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 100, 40),
new Foo(4, "P4", 820, 1100)
Run Code Online (Sandbox Code Playgroud)
我现在写的:
data.stream()
.???
.collect(Collectors.groupingBy(PlannedProjectPOJO::getId));
Run Code Online (Sandbox Code Playgroud)
我怎样才能做到这一点?
Hol*_*ger 67
使用Collectors.groupingBy是正确的方法,但不是使用单个参数版本,它将为每个组创建所有项目的列表,您应该使用两个arg版本,另一个Collector决定如何聚合每个组的元素.
当您想要聚合元素的单个属性或只计算每个组的元素数时,这一点尤其顺利:
数数:
list.stream()
.collect(Collectors.groupingBy(foo -> foo.id, Collectors.counting()))
.forEach((id,count)->System.out.println(id+"\t"+count));
Run Code Online (Sandbox Code Playgroud)总结一个属性:
list.stream()
.collect(Collectors.groupingBy(foo -> foo.id,
Collectors.summingInt(foo->foo.targetCost)))
.forEach((id,sumTargetCost)->System.out.println(id+"\t"+sumTargetCost));
Run Code Online (Sandbox Code Playgroud)在您想要聚合多个属性的情况下,指定自定义缩减操作(如本答案中建议的那样)是正确的方法,但是,您可以在分组操作期间执行缩减操作,因此无需将整个数据收集到a Map<…,List>在执行减少之前:
(我假设你import static java.util.stream.Collectors.*;现在用...)
list.stream().collect(groupingBy(foo -> foo.id, collectingAndThen(reducing(
(a,b)-> new Foo(a.id, a.ref, a.targetCost+b.targetCost, a.actualCost+b.actualCost)),
Optional::get)))
.forEach((id,foo)->System.out.println(foo));
Run Code Online (Sandbox Code Playgroud)
为了完整起见,这里有一个超出问题范围的问题的解决方案:如果您想要GROUP BY多个列/属性,该怎么办?
跳到程序员脑海中的第一件事是用于groupingBy提取流的元素的属性并创建/返回一个新的密钥对象.但是这需要为关键属性提供适当的持有者类(而Java没有通用的Tuple类).
但还有另一种选择.通过使用三个arg形式,groupingBy我们可以为实际Map实现指定供应商,这将确定密钥相等性.通过使用带有比较器的有序映射来比较多个属性,我们可以获得所需的行为,而无需额外的类.我们只需要注意不要使用我们的比较器忽略的键实例中的属性,因为它们只有任意值:
list.stream().collect(groupingBy(Function.identity(),
()->new TreeMap<>(
// we are effectively grouping by [id, actualCost]
Comparator.<Foo,Integer>comparing(foo->foo.id).thenComparing(foo->foo.actualCost)
), // and aggregating/ summing targetCost
Collectors.summingInt(foo->foo.targetCost)))
.forEach((group,targetCostSum) ->
// take the id and actualCost from the group and actualCost from aggregation
System.out.println(group.id+"\t"+group.actualCost+"\t"+targetCostSum));
Run Code Online (Sandbox Code Playgroud)
Dic*_*ici 15
这是一种可能的方法:
public class Test {
private static class Foo {
public int id, targetCost, actualCost;
public String ref;
public Foo(int id, String ref, int targetCost, int actualCost) {
this.id = id;
this.targetCost = targetCost;
this.actualCost = actualCost;
this.ref = ref;
}
@Override
public String toString() {
return String.format("Foo(%d,%s,%d,%d)",id,ref,targetCost,actualCost);
}
}
public static void main(String[] args) {
List<Foo> list = Arrays.asList(
new Foo(1, "P1", 300, 400),
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 30, 20),
new Foo(3, "P3", 70, 20),
new Foo(1, "P1", 360, 40),
new Foo(4, "P4", 320, 200),
new Foo(4, "P4", 500, 900));
List<Foo> transform = list.stream()
.collect(Collectors.groupingBy(foo -> foo.id))
.entrySet().stream()
.map(e -> e.getValue().stream()
.reduce((f1,f2) -> new Foo(f1.id,f1.ref,f1.targetCost + f2.targetCost,f1.actualCost + f2.actualCost)))
.map(f -> f.get())
.collect(Collectors.toList());
System.out.println(transform);
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
[Foo(1,P1,660,440), Foo(2,P2,600,400), Foo(3,P3,100,40), Foo(4,P4,820,1100)]
Run Code Online (Sandbox Code Playgroud)
Stream仅使用 JDK 的API执行此操作并不像其他答案所示那样简单。本文解释了如何GROUP BY在 Java 8(使用标准聚合函数)和使用jOO 中实现 SQL 语义?,一个扩展Stream这些用例的库。
写:
import static org.jooq.lambda.tuple.Tuple.tuple;
import java.util.List;
import java.util.stream.Collectors;
import org.jooq.lambda.Seq;
import org.jooq.lambda.tuple.Tuple;
// ...
List<Foo> list =
// FROM Foo
Seq.of(
new Foo(1, "P1", 300, 400),
new Foo(2, "P2", 600, 400),
new Foo(3, "P3", 30, 20),
new Foo(3, "P3", 70, 20),
new Foo(1, "P1", 360, 40),
new Foo(4, "P4", 320, 200),
new Foo(4, "P4", 500, 900))
// GROUP BY f1, f2
.groupBy(
x -> tuple(x.f1, x.f2),
// SELECT SUM(f3), SUM(f4)
Tuple.collectors(
Collectors.summingInt(x -> x.f3),
Collectors.summingInt(x -> x.f4)
)
)
// Transform the Map<Tuple2<Integer, String>, Tuple2<Integer, Integer>> type to List<Foo>
.entrySet()
.stream()
.map(e -> new Foo(e.getKey().v1, e.getKey().v2, e.getValue().v1, e.getValue().v2))
.collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)
打电话
System.out.println(list);
Run Code Online (Sandbox Code Playgroud)
然后会屈服
[Foo [f1=1, f2=P1, f3=660, f4=440],
Foo [f1=2, f2=P2, f3=600, f4=400],
Foo [f1=3, f2=P3, f3=100, f4=40],
Foo [f1=4, f2=P4, f3=820, f4=1100]]
Run Code Online (Sandbox Code Playgroud)
小智 5
data.stream().collect(toMap(foo -> foo.id,
Function.identity(),
(a, b) -> new Foo(a.getId(),
a.getNum() + b.getNum(),
a.getXXX(),
a.getYYY()))).values();
Run Code Online (Sandbox Code Playgroud)
只需使用toMap(),非常简单