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
idby 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'}]}
