将流映射到另一个对象的实例
Mapping a stream to an instance of another object
我有一个 class CarPart
定义为:
class CarPart {
String name;
BigDecimal price;
Supplier supplier;
}
A class Report
:
class Report {
List<Part> parts;
BigDecimal total;
}
和一个class Part
:
class Part {
String name;
String supplierName;
}
给定一个 Stream<CarPart> carParts
,我需要创建一个 Report
对象。
我的想法是创建一个 Map<List<Part>, BigDecimal>
,其中 List<Part>
是转换后的 CarPart
对象的列表,BigDecimal
是总和给定流中所有汽车零件的价格。之后,我可以访问包含单个条目的 Map<>
,并且可以创建一个新的 Report
.
我开始这样做了,但我不确定如何收集它。在下面我做的 .map
之后,我实际上有一个 Map<Part, BigDecimal>
但我需要总结列表中的所有 Part
对象,并添加所有 BigDecimal
来创建Report
.
的总价值
carParts.stream()
.map(x -> {
return new AbstractMap.SimpleEntry<>(new Part(x.getName(), x.supplier.getName()), x.getPrice());
})
.collect(.....)
我是不是处理的完全错误?我试图只迭代一次流。
P.S:假设所有的getters和setters都可用。
Java 12+ 解
如果您 Java 12+:
carParts.collect(teeing(
mapping(p -> new Part(p.name, p.supplier.name), toList()),
mapping(p -> p.price, reducing(BigDecimal.ZERO, BigDecimal::add)),
Report::new
));
假设这个静态导入:
import static java.util.stream.Collectors.*;
使用第三方收集器的解决方案
如果可以选择提供元组和元组收集器的第三方库(例如 jOOλ),您甚至可以在 Java 12
之前执行此操作
carParts.collect(Tuple.collectors(
mapping(p -> new Part(p.name, p.supplier.name), toList()),
mapping(p -> p.price, reducing(BigDecimal.ZERO, BigDecimal::add))
)).map(Report::new);
如果你愿意,你可以自己滚动 Tuple.collectors()
,当然,将 Tuple2
替换为 Map.Entry
:
static <T, A1, A2, D1, D2> Collector<T, Tuple2<A1, A2>, Tuple2<D1, D2>> collectors(
Collector<? super T, A1, D1> collector1
, Collector<? super T, A2, D2> collector2
) {
return Collector.<T, Tuple2<A1, A2>, Tuple2<D1, D2>>of(
() -> tuple(
collector1.supplier().get()
, collector2.supplier().get()
),
(a, t) -> {
collector1.accumulator().accept(a.v1, t);
collector2.accumulator().accept(a.v2, t);
},
(a1, a2) -> tuple(
collector1.combiner().apply(a1.v1, a2.v1)
, collector2.combiner().apply(a1.v2, a2.v2)
),
a -> tuple(
collector1.finisher().apply(a.v1)
, collector2.finisher().apply(a.v2)
)
);
}
免责声明:我制作了 jOOλ
仅使用 Java 8 的解决方案并继续您的尝试
你在评论中要求我完成你已经开始的工作。我不认为这是正确的方法。正确的方法是实现一个与 JDK 12 收集器 Collectors.teeing()
做同样事情的收集器(或使用建议的第三方收集器,我不明白为什么这不是一个选项) .
但现在开始,这是您完成已开始工作的一种方式:
carParts
// Stream<SimpleEntry<Part, BigDecimal>>
.map(x -> new AbstractMap.SimpleEntry<>(
new Part(x.name, x.supplier.name), x.price))
// Map<Integer, Report>
.collect(Collectors.toMap(
// A dummy map key. I don't really need it, I just want access to
// the Collectors.toMap()'s mergeFunction
e -> 1,
// A single entry report. This just shows that the intermediate
// step of creating a Map.Entry wasn't really useful anyway
e -> new Report(
Collections.singletonList(e.getKey()),
e.getValue()),
// Merging two intermediate reports
(r1, r2) -> {
List<Part> parts = new ArrayList<>();
parts.addAll(r1.parts);
parts.addAll(r2.parts);
return new Report(parts, r1.total.add(r2.total));
}
// We never needed the map.
)).get(1);
还有很多其他方法可以做类似的事情。您还可以使用 Stream.collect(Supplier, BiConsumer, BiConsumer)
重载来实现临时收集器,或使用 Collector.of()
创建一个。
但真的。使用 Collectors.teeing()
的一些变体。甚至是命令式循环,而不是上面的循环。
带有可变 Report
class
当 Report
class 是可变的并且您有修改它的必要权限时,您可以使用
Report report = carParts.stream()
.collect(
() -> new Report(new ArrayList<>(), BigDecimal.ZERO),
(r, cp) -> {
r.parts.add(new Part(cp.getName(), cp.supplier.getName()));
r.total = r.total.add(cp.getPrice());
},
(r1, r2) -> { r1.parts.addAll(r2.parts); r1.total = r1.total.add(r2.total); });
具有不可变的 Report
class
当你不能修改Report
个实例时,你必须使用一个临时的可变对象进行处理,然后创建一个最终的结果对象。否则,操作类似:
Report report = carParts.stream()
.collect(Collector.of(
() -> new Object() {
List<Part> parts = new ArrayList<>();
BigDecimal total = BigDecimal.ZERO;
},
(r, cp) -> {
r.parts.add(new Part(cp.getName(), cp.supplier.getName()));
r.total = r.total.add(cp.getPrice());
},
(r1, r2) -> {
r1.parts.addAll(r2.parts);
r1.total = r1.total.add(r2.total);
return r1;
},
tmp -> new Report(tmp.parts, tmp.total)));
好吧,原则上,您不需要可变对象,但可以将操作实现为纯归约,但是 Mutable Reduction 又名 collect
操作对于此特定目的(即将值收集到 List
).
时
我有一个 class CarPart
定义为:
class CarPart {
String name;
BigDecimal price;
Supplier supplier;
}
A class Report
:
class Report {
List<Part> parts;
BigDecimal total;
}
和一个class Part
:
class Part {
String name;
String supplierName;
}
给定一个 Stream<CarPart> carParts
,我需要创建一个 Report
对象。
我的想法是创建一个 Map<List<Part>, BigDecimal>
,其中 List<Part>
是转换后的 CarPart
对象的列表,BigDecimal
是总和给定流中所有汽车零件的价格。之后,我可以访问包含单个条目的 Map<>
,并且可以创建一个新的 Report
.
我开始这样做了,但我不确定如何收集它。在下面我做的 .map
之后,我实际上有一个 Map<Part, BigDecimal>
但我需要总结列表中的所有 Part
对象,并添加所有 BigDecimal
来创建Report
.
carParts.stream()
.map(x -> {
return new AbstractMap.SimpleEntry<>(new Part(x.getName(), x.supplier.getName()), x.getPrice());
})
.collect(.....)
我是不是处理的完全错误?我试图只迭代一次流。
P.S:假设所有的getters和setters都可用。
Java 12+ 解
如果您 Java 12+:
carParts.collect(teeing(
mapping(p -> new Part(p.name, p.supplier.name), toList()),
mapping(p -> p.price, reducing(BigDecimal.ZERO, BigDecimal::add)),
Report::new
));
假设这个静态导入:
import static java.util.stream.Collectors.*;
使用第三方收集器的解决方案
如果可以选择提供元组和元组收集器的第三方库(例如 jOOλ),您甚至可以在 Java 12
之前执行此操作carParts.collect(Tuple.collectors(
mapping(p -> new Part(p.name, p.supplier.name), toList()),
mapping(p -> p.price, reducing(BigDecimal.ZERO, BigDecimal::add))
)).map(Report::new);
如果你愿意,你可以自己滚动 Tuple.collectors()
,当然,将 Tuple2
替换为 Map.Entry
:
static <T, A1, A2, D1, D2> Collector<T, Tuple2<A1, A2>, Tuple2<D1, D2>> collectors(
Collector<? super T, A1, D1> collector1
, Collector<? super T, A2, D2> collector2
) {
return Collector.<T, Tuple2<A1, A2>, Tuple2<D1, D2>>of(
() -> tuple(
collector1.supplier().get()
, collector2.supplier().get()
),
(a, t) -> {
collector1.accumulator().accept(a.v1, t);
collector2.accumulator().accept(a.v2, t);
},
(a1, a2) -> tuple(
collector1.combiner().apply(a1.v1, a2.v1)
, collector2.combiner().apply(a1.v2, a2.v2)
),
a -> tuple(
collector1.finisher().apply(a.v1)
, collector2.finisher().apply(a.v2)
)
);
}
免责声明:我制作了 jOOλ
仅使用 Java 8 的解决方案并继续您的尝试
你在评论中要求我完成你已经开始的工作。我不认为这是正确的方法。正确的方法是实现一个与 JDK 12 收集器 Collectors.teeing()
做同样事情的收集器(或使用建议的第三方收集器,我不明白为什么这不是一个选项) .
但现在开始,这是您完成已开始工作的一种方式:
carParts
// Stream<SimpleEntry<Part, BigDecimal>>
.map(x -> new AbstractMap.SimpleEntry<>(
new Part(x.name, x.supplier.name), x.price))
// Map<Integer, Report>
.collect(Collectors.toMap(
// A dummy map key. I don't really need it, I just want access to
// the Collectors.toMap()'s mergeFunction
e -> 1,
// A single entry report. This just shows that the intermediate
// step of creating a Map.Entry wasn't really useful anyway
e -> new Report(
Collections.singletonList(e.getKey()),
e.getValue()),
// Merging two intermediate reports
(r1, r2) -> {
List<Part> parts = new ArrayList<>();
parts.addAll(r1.parts);
parts.addAll(r2.parts);
return new Report(parts, r1.total.add(r2.total));
}
// We never needed the map.
)).get(1);
还有很多其他方法可以做类似的事情。您还可以使用 Stream.collect(Supplier, BiConsumer, BiConsumer)
重载来实现临时收集器,或使用 Collector.of()
创建一个。
但真的。使用 Collectors.teeing()
的一些变体。甚至是命令式循环,而不是上面的循环。
带有可变 Report
class
当 Report
class 是可变的并且您有修改它的必要权限时,您可以使用
Report report = carParts.stream()
.collect(
() -> new Report(new ArrayList<>(), BigDecimal.ZERO),
(r, cp) -> {
r.parts.add(new Part(cp.getName(), cp.supplier.getName()));
r.total = r.total.add(cp.getPrice());
},
(r1, r2) -> { r1.parts.addAll(r2.parts); r1.total = r1.total.add(r2.total); });
具有不可变的 Report
class
当你不能修改Report
个实例时,你必须使用一个临时的可变对象进行处理,然后创建一个最终的结果对象。否则,操作类似:
Report report = carParts.stream()
.collect(Collector.of(
() -> new Object() {
List<Part> parts = new ArrayList<>();
BigDecimal total = BigDecimal.ZERO;
},
(r, cp) -> {
r.parts.add(new Part(cp.getName(), cp.supplier.getName()));
r.total = r.total.add(cp.getPrice());
},
(r1, r2) -> {
r1.parts.addAll(r2.parts);
r1.total = r1.total.add(r2.total);
return r1;
},
tmp -> new Report(tmp.parts, tmp.total)));
好吧,原则上,您不需要可变对象,但可以将操作实现为纯归约,但是 Mutable Reduction 又名 collect
操作对于此特定目的(即将值收集到 List
).