I have a simple nested structure as such:
public static class A {
private List<B> classBList;
// constructor, getters, etc.
}
public static class B {
private int id;
private String name;
// constructor, getters, etc.
}
I want to create a map of <Integer,List<A>>
where the integer field in class B
id
will be the key, and the A
objects in the input that contain the matching id
will be rolled up into a list as the value. The input would be a list of class A
.
So for example:
Input:
[classBList=[B{id:1, name:"Hello"}, B{id:1, name:"Hi"}, B{id:1, name:"Bye"}, B{id:1, name:"Yes"}],
classBList=[B{id:2, name:"No"}, B{id:2, name:"Go"}, B{id:2, name:"Yellow"}],
classBList=[B{id:2, name:"Joe"}, B{id:2, name:"Blow"}]]
(3 elements in a list of A objects: 4 elements in classBList for first element, 3 elements in classBList for second element, 2 elements in classBList for third element)
Each B
element in a classBList will have the same id
.
Output:
{Key=1, Value=[ A{classBList=[B{id:1, name:"Hello"}, B{id:1, name:"Hi"}, B{id:1, name:"Bye"}, B{id:1, name:"Yes"}]} ]
{Key=2, Value=[ A{classBList=[B{id:2, name:"No"}, B{id:2, name:"Go"}, B{id:2, name:"Yellow"}, B{id:2, name:"Joe"}, B{id:2, name:"Blow"}]} ]
I'm having trouble, however, writing the lambdas that allow this to happen. What I tried:
Map<Integer, List<A>> heyThere = classAListInput.stream()
.collect(Collectors.toMap(
A::getClass,
element -> element.getClassBList().stream()
.map(B::getId)
.collect(Collectors.toList())
));
But this doesn't compile, so really not sure of how the syntax should look.
If you're wondering why not just alter the map so it's <Integer, List< B >>, there are other fields in class A that I didn't note but would be needed in the output, so that's why a list of A objects would be the value in the map.
CodePudding user response:
You'll need to flat map to some sort of tuple class, like AbstractMap.SimpleEntry
, so you can stream A
and B
in parallel and then invert the grouping:
classAListInput.stream()
.flatMap(a -> a.getClassBList()
.stream()
.map(b -> new SimpleEntry<>(b.getId(), a))
.collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toList())))
CodePudding user response:
It seems that you need to rebuild instances of A
class with the new list of B
.
However, expected output shows that there is only one A
entry in the list, and all B
's are added to the same A
instance:
{Key=2, Value=[ A{classBList=[B{id:2, name:"No"}, B{id:2, name: "Go"}, B{id:2, name:"Yellow"}, B{id:2, name:"Joe"}, B{id:2, name:"Blow"}]} ]
So, the following implementation may be offered assuming there's an all-args constructor in A
class accepting List<B>
:
Map<Integer, List<A>> result = classAListInput
.stream() // Stream<A>
.flatMap(a -> a.getClassBList().stream()) // Stream<B>
.collect(Collectors.groupingBy(
B::getId,
Collectors.collectingAndThen(
Collectors.toList(), // List<B> flattening all B instances by id
lst -> List.of(new A(lst)) // or Arrays.asList or Collections.singletonList
)
));
CodePudding user response:
If I understood the problem correctly, judging by the sample data, you have a List<List<B>>
as an input.
And based on the sample output you've provided, you need to obtain a map of type Map<Integer,A>
as a result (not a Map<Integer,List<A>>
).
This can be done in the following steps:
- flatten the data using
flatMap()
, i.e. transform aStream<List<B>>
into aStream<B>
; - group the elements by
id
by the means of collectorgroupingBy()
; - collect the elements mapped to the same key into a list and transform them into an object
A
, which can be done by applying a combination of collectorscollectingAndThen()
andtoList()
as the downstream ofgroupingBy()
.
That's how it might be implemented:
public static void main(String[] args) {
List<List<B>> classAListInput = List.of(
List.of(new B(1, "Hello"), new B(1, "Hi"), new B(1, "Bye"), new B(1, "Yes")),
List.of(new B(2, "No"), new B(2, "Go"), new B(2, "Yellow")),
List.of(new B(2, "Joe"), new B(2, "Blow"))
);
Map<Integer, A> aById = classAListInput.stream()
.flatMap(Collection::stream) // flattening the data
.collect(Collectors.groupingBy(
B::getId, // grouping by id
Collectors.collectingAndThen(
Collectors.toList(), // accumulating elements into a list
A::new) // instantiating object A based on the List<B>
));
aById.forEach((id, a) -> System.out.println(id " -> " a));
}
Output:
1 -> A{classBList=[B{id=1, name='Hello'}, B{id=1, name='Hi'}, B{id=1, name='Bye'}, B{id=1, name='Yes'}]}
2 -> A{classBList=[B{id=2, name='No'}, B{id=2, name='Go'}, B{id=2, name='Yellow'}, B{id=2, name='Joe'}, B{id=2, name='Blow'}]}