在嵌套 Map 中使用 groupingBy,但收集到不同类型的对象
Using groupingBy into a nested Map, but collecting to a different type of object
所以我有这样的代码 "works"(为简单起见替换了一些名称):
Map<String, Map<String, ImmutableList<SomeClassA>>> someMap =
someListOfClassA.stream()
.filter(...)
.collect(Collectors.groupingBy(SomeClassA::someCriteriaA,
Collectors.groupingBy(SomeClassA::someCriteriaB, GuavaCollectors.toImmutableList()
)
));
但是,我想更改此代码,以便在按 SomeClassA 字段分组后,内部集合属于 SomeClassB。例如,如果 类 看起来像这样:
假设他们都有所有 args 构造函数
class SomeClassA {
String someCriteriaA;
String someCriteriaB;
T someData;
String someId;
}
class SomeClassB {
T someData;
String someId;
}
某处有一个方法:
public static Collection<SomeClassB> getSomeClassBsFromSomeClassA(SomeClassA someA) {
List<Some List of Class B> listOfB = someMethod(someA);
return listOfB; // calls something using someClassA, gets a list of SomeClassB
}
我想将 SomeClass B 的结果列表展平为
Map<String, Map<String, ImmutableList<SomeClassB>>> someMap =
someListOfClassA.stream()
.filter(...)
. // not sure how to group by SomeClassA fields but result in a list of SomeClassB since one SomeClassA can result in multiple SomeClassB
我不确定这如何适合上面的代码。我怎样才能将一堆基于 SomeClassB 的列表收集到一个包含 SomeClassA 的所有值的列表中?如果单个 ClassA 映射到单个 ClassB,我知道如何使用 Collectors.mapping 让它工作,但由于每个 ClassA 导致多个 ClassB,我不确定如何让它工作。
如有任何想法,我们将不胜感激。谢谢!
使用像这样的自定义收集器:
private static Collector<Collection<SomeClassB>, ?, ImmutableList<SomeClassB>>
flatMapToImmutableList() {
return Collectors.collectingAndThen(Collectors.toList(),
listOfCollectionsOfB ->
listOfCollectionsOfB.stream()
.flatMap(Collection::stream)
.collect(GuavaCollectors.toImmutableList()));
}
你可以实现你所追求的:
Map<String, Map<String, List<SomeClassB>>> someMap =
someListOfClassA.stream()
.filter(...)
.collect(Collectors.groupingBy(SomeClassA::getSomeCriteriaA,
Collectors.groupingBy(SomeClassA::getSomeCriteriaB,
Collectors.mapping(a -> getSomeClassBsFromSomeClassA(a),
flatMapToImmutableList()))));
虽然我们都在等待 Java 9 的 Collectors.flatMapping
(感谢@shmosel 提供 link),但您可以编写自己的收集器来实现您想要的:
public static <T, D, R> Collector<T, ?, R> flatMapping(
Function<? super T, ? extends Stream<? extends D>> streamMapper,
Collector<? super D, ?, R> downstream) {
class Acc {
Stream.Builder<Stream<? extends D>> builder = Stream.builder();
void add(T t) {
builder.accept(streamMapper.apply(t));
}
Acc combine(Acc another) {
another.builder.build().forEach(builder);
return this;
}
R finish() {
return builder.build()
.flatMap(Function.identity()) // Here!
.collect(downstream);
}
}
return Collector.of(Acc::new, Acc::add, Acc::combine, Acc::finish);
}
此辅助方法使用 Collector.of
and a local class Acc
to accumulate the streams returned by the provided streamMapper
function, which takes an element of the original stream as an argument. These streams are accumulated in a Stream.Builder
,它将在应用收集器的整理器函数时构建。
在构建这个流流之后,它会立即与身份函数进行平面映射,因为我们只想连接流。 (我本可以使用流列表而不是流的流,但我认为 Stream.Builder
既非常有效又未被充分利用)。
Acc
还实现了一个组合器方法,它将给定的 Acc
的流合并到这个 Acc
的流构建器中。仅当原始流是并行时才会使用此功能。
以下是如何在您的示例中使用此方法:
Map<String, Map<String, ImmutableList<SomeClassB>>> map = someListOfClassA.stream()
.filter(...)
.collect(
Collectors.groupingBy(SomeClassA::getSomeCriteriaA,
Collectors.groupingBy(SomeClassA::getSomeCriteriaB,
flatMapping(
a -> getSomeClassBsFromSomeClassA(a).stream(),
ImmutableList.toImmutableList()))));
编辑: 正如@Holger 在下面的评论中指出的那样,累积时不需要将数据缓冲到流生成器中。相反,平面映射收集器可以通过在累加器函数中执行展平权来实现。 ,我在他的同意下逐字复制在这里:
public static <T, U, A, R> Collector<T, ?, R> flatMapping(
Function<? super T, ? extends Stream<? extends U>> mapper,
Collector<? super U, A, R> downstream) {
BiConsumer<A, ? super U> acc = downstream.accumulator();
return Collector.of(downstream.supplier(),
(a, t) -> {
try (Stream<? extends U> s = mapper.apply(t)) {
if (s != null) s.forEachOrdered(u -> acc.accept(a, u));
}
},
downstream.combiner(), downstream.finisher(),
downstream.characteristics().toArray(new Collector.Characteristics[0]));
}
所以我有这样的代码 "works"(为简单起见替换了一些名称):
Map<String, Map<String, ImmutableList<SomeClassA>>> someMap =
someListOfClassA.stream()
.filter(...)
.collect(Collectors.groupingBy(SomeClassA::someCriteriaA,
Collectors.groupingBy(SomeClassA::someCriteriaB, GuavaCollectors.toImmutableList()
)
));
但是,我想更改此代码,以便在按 SomeClassA 字段分组后,内部集合属于 SomeClassB。例如,如果 类 看起来像这样:
假设他们都有所有 args 构造函数
class SomeClassA {
String someCriteriaA;
String someCriteriaB;
T someData;
String someId;
}
class SomeClassB {
T someData;
String someId;
}
某处有一个方法:
public static Collection<SomeClassB> getSomeClassBsFromSomeClassA(SomeClassA someA) {
List<Some List of Class B> listOfB = someMethod(someA);
return listOfB; // calls something using someClassA, gets a list of SomeClassB
}
我想将 SomeClass B 的结果列表展平为
Map<String, Map<String, ImmutableList<SomeClassB>>> someMap =
someListOfClassA.stream()
.filter(...)
. // not sure how to group by SomeClassA fields but result in a list of SomeClassB since one SomeClassA can result in multiple SomeClassB
我不确定这如何适合上面的代码。我怎样才能将一堆基于 SomeClassB 的列表收集到一个包含 SomeClassA 的所有值的列表中?如果单个 ClassA 映射到单个 ClassB,我知道如何使用 Collectors.mapping 让它工作,但由于每个 ClassA 导致多个 ClassB,我不确定如何让它工作。
如有任何想法,我们将不胜感激。谢谢!
使用像这样的自定义收集器:
private static Collector<Collection<SomeClassB>, ?, ImmutableList<SomeClassB>>
flatMapToImmutableList() {
return Collectors.collectingAndThen(Collectors.toList(),
listOfCollectionsOfB ->
listOfCollectionsOfB.stream()
.flatMap(Collection::stream)
.collect(GuavaCollectors.toImmutableList()));
}
你可以实现你所追求的:
Map<String, Map<String, List<SomeClassB>>> someMap =
someListOfClassA.stream()
.filter(...)
.collect(Collectors.groupingBy(SomeClassA::getSomeCriteriaA,
Collectors.groupingBy(SomeClassA::getSomeCriteriaB,
Collectors.mapping(a -> getSomeClassBsFromSomeClassA(a),
flatMapToImmutableList()))));
虽然我们都在等待 Java 9 的 Collectors.flatMapping
(感谢@shmosel 提供 link),但您可以编写自己的收集器来实现您想要的:
public static <T, D, R> Collector<T, ?, R> flatMapping(
Function<? super T, ? extends Stream<? extends D>> streamMapper,
Collector<? super D, ?, R> downstream) {
class Acc {
Stream.Builder<Stream<? extends D>> builder = Stream.builder();
void add(T t) {
builder.accept(streamMapper.apply(t));
}
Acc combine(Acc another) {
another.builder.build().forEach(builder);
return this;
}
R finish() {
return builder.build()
.flatMap(Function.identity()) // Here!
.collect(downstream);
}
}
return Collector.of(Acc::new, Acc::add, Acc::combine, Acc::finish);
}
此辅助方法使用 Collector.of
and a local class Acc
to accumulate the streams returned by the provided streamMapper
function, which takes an element of the original stream as an argument. These streams are accumulated in a Stream.Builder
,它将在应用收集器的整理器函数时构建。
在构建这个流流之后,它会立即与身份函数进行平面映射,因为我们只想连接流。 (我本可以使用流列表而不是流的流,但我认为 Stream.Builder
既非常有效又未被充分利用)。
Acc
还实现了一个组合器方法,它将给定的 Acc
的流合并到这个 Acc
的流构建器中。仅当原始流是并行时才会使用此功能。
以下是如何在您的示例中使用此方法:
Map<String, Map<String, ImmutableList<SomeClassB>>> map = someListOfClassA.stream()
.filter(...)
.collect(
Collectors.groupingBy(SomeClassA::getSomeCriteriaA,
Collectors.groupingBy(SomeClassA::getSomeCriteriaB,
flatMapping(
a -> getSomeClassBsFromSomeClassA(a).stream(),
ImmutableList.toImmutableList()))));
编辑: 正如@Holger 在下面的评论中指出的那样,累积时不需要将数据缓冲到流生成器中。相反,平面映射收集器可以通过在累加器函数中执行展平权来实现。
public static <T, U, A, R> Collector<T, ?, R> flatMapping(
Function<? super T, ? extends Stream<? extends U>> mapper,
Collector<? super U, A, R> downstream) {
BiConsumer<A, ? super U> acc = downstream.accumulator();
return Collector.of(downstream.supplier(),
(a, t) -> {
try (Stream<? extends U> s = mapper.apply(t)) {
if (s != null) s.forEachOrdered(u -> acc.accept(a, u));
}
},
downstream.combiner(), downstream.finisher(),
downstream.characteristics().toArray(new Collector.Characteristics[0]));
}