I have something like the below :
public class MyClass {
private Long stackId
private Long questionId
}
A collection of say 100, where the stackid could be duplicate with different questionIds. Its a one to many relationship between stackId and questionId
Is there a streamy, java 8 way to convert to the below strcuture :
public class MyOtherClass {
private Long stackId
private Collection<Long> questionIds
}
Which would be a collection of 25, with each instance having a nested collection of 4 questionIds.
Input :
[{1,100},{1,101},{1,102},{1,103},{2,200},{2,201},{2,202},{1,203}]
Output
[{1, [100,101,102,103]},{2,[200,201,202,203]}]
The straight-forward way with the Stream API involves 2 Stream pipelines:
- The first one creates a temporary
Map<Long, List<Long>>
of stackId
to questionIds
. This is done with the groupingBy(classifier, downstream)
collectors where we classify per the stackId
and values having the same stackId
are mapped to their questionId
(with mapping
) and collected into a list with toList()
.
- The second one converts each entry of that map into a
MyOtherClass
instance and collects that into a list.
Assuming you have a constructor MyOtherClass(Long stackId, Collection<Long> questionIds)
, a sample code would be:
Map<Long, List<Long>> map =
list.stream()
.collect(Collectors.groupingBy(
MyClass::getStackId,
Collectors.mapping(MyClass::getQuestionId, Collectors.toList())
));
List<MyOtherClass> result =
map.entrySet()
.stream()
.map(e -> new MyOtherClass(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Using StreamEx library, you could do that in a single Stream pipeline. This library offers a pairing
and first
collectors. This enables to pair two collectors and perform a finisher operation on the two collected results:
- The first one only keeps the first
stackId
of the grouped elements (they will all be the same, by construction)
- The second one mapping each element into their
questionId
and collecting into a list.
- The finisher operation just returns a new instance of
MyOtherClass
.
Sample code:
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static one.util.streamex.MoreCollectors.first;
import static one.util.streamex.MoreCollectors.pairing;
// ...
Collection<MyOtherClass> result =
StreamEx.of(list)
.groupingBy(
MyClass::getStackId,
pairing(
collectingAndThen(mapping(MyClass::getStackId, first()), Optional::get),
mapping(MyClass::getQuestionId, toList()),
MyOtherClass::new
)
).values();
List<MyClass> inputs = Arrays.asList(
new MyClass(1L, 100L),
new MyClass(1L, 101L),
new MyClass(1L, 102L),
new MyClass(1L, 103L),
new MyClass(2L, 200L),
new MyClass(2L, 201L),
new MyClass(2L, 202L),
new MyClass(2L, 203L)
);
Map<Long, List<Long>> result = inputs
.stream()
.collect(
Collectors.groupingBy(MyClass::getStackId,
Collectors.mapping(
MyClass::getQuestionId,
Collectors.toList()
)
)
);
You can use the java8 groupingBy collector. Like this:
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class RandomTest {
class MyClass {
private Long stackId;
private Long questionId;
public MyClass(Long stackId, Long questionId) {
this.stackId = stackId;
this.questionId = questionId;
}
public Long getStackId() {
return stackId;
}
public Long getQuestionId() {
return questionId;
}
}
public class MyOtherClass {
private Long stackId;
private Set<Long> questionIds;
public MyOtherClass(Long stackId, Set<Long> questionIds) {
this.stackId = stackId;
this.questionIds = questionIds;
}
public Long getStackId() {
return stackId;
}
public Set<Long> getQuestionIds() {
return questionIds;
}
}
@Test
public void test() {
List<MyClass> classes = new ArrayList<>();
List<MyOtherClass> otherClasses = new ArrayList<>();
//populate the classes list
for (int j = 1; j <= 25; j++) {
for (int i = 0; i < 4; i++) {
classes.add(new MyClass(0L + j, (100L*j) + i));
}
}
//populate the otherClasses List
classes.stream().collect(Collectors
.groupingBy(MyClass::getStackId, Collectors.mapping(MyClass::getQuestionId, Collectors.toSet())))
.entrySet().stream().forEach(
longSetEntry -> otherClasses.add(new MyOtherClass(longSetEntry.getKey(), longSetEntry.getValue())));
//print the otherClasses list
otherClasses.forEach(myOtherClass -> {
System.out.print(myOtherClass.getStackId() + ": [");
myOtherClass.getQuestionIds().forEach(questionId-> System.out.print(questionId + ","));
System.out.println("]");
});
}
}