Imagine a simple object with 3 attributes:
public class Obj {
boolean toBeAdded;
String type;
int order;
public Obj(boolean toBeAdded, String type, int order) {
this.toBeAdded = toBeAdded;
this.type = type;
this.order = order;
}
public boolean isToBeAdded() {
return toBeAdded;
}
public String getType() {
return type;
}
public int getOrder() {
return order;
}
}
Imagine I have a List of several Objs, with different types:
import java.util.Arrays;
import java.util.List;
public class Utils {
static List createA(){
List list = Arrays.asList(
new Obj(true, "A", 1),
new Obj(false, "A", 2),
new Obj(true, "A", 3),
new Obj(false, "A", 4)
);
return list;
}
static List createB(){
List list = Arrays.asList(
new Obj(true, "B", 1),
new Obj(false, "B", 2),
new Obj(true, "B", 3),
new Obj(false, "B", 4)
);
return list;
}
static List createC(){
List list = Arrays.asList(
new Obj(false, "C", 1),
new Obj(false, "C", 2),
new Obj(true, "C", 3),
new Obj(true, "C", 4)
);
return list;
}
}
I want to somehow filter this list and have the latest Obj (highest order value) for each type that can be added (toBeAdded = true).
For this example, the result should be a list with:
Obj1: type 1, order 3
Obj2: type 2, order 3
Obj3: type 3, order 4
I know how to filter and how to sort, but I still couldn't understand how to get the first in each subtype. Is this against Stream rules? Since what I want to do is basically parse N different substreams, for N different types?
Here is what I could do so far:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class MainTest {
public static void main(String[] args) {
List list = new ArrayList<>();
list.addAll(Utils.createA());
list.addAll(Utils.createB());
list.addAll(Utils.createC());
System.out.println(list);
List filteredList = list
.stream()
.filter(Obj::isToBeAdded)
.sorted(Comparator.comparingInt(Obj::getOrder).reversed())
.collect(Collectors.toList());
System.out.println(filteredList);
}
}
However, this is only doing the easy part - filtering and ordering. I still need to do something like findFirst() for each type. Is there any way to do this?
Do I have to do 3 different streams (one for each type) and then merge the lists? Is there a way to do this without knowing how many types we will have?
I also read about collect(Collectors.groupingBy()) but this would create different maps for each type, which I don't want.
解决方案
Using groupingBy can help here along with a downstream of minBy/maxBy. This would provide a map to look up if the value after filtering is present per type.
Map> groupMaxOrderByType = list.stream()
.filter(Obj::isToBeAdded)
.collect(Collectors.groupingBy(Obj::getType,
Collectors.maxBy(Comparator.comparingInt(Obj::getOrder)))); //highest order
The lookup or access to these objects would transform into something like:
Obj maxPerTypeA = groupMaxOrderByType.get("A").orElse.. // similar for each type
Edit: Or if you were to collect all such present types into a final result, you could follow to access the values of the Map.
List result = groupMaxOrderByType.values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
Edit: Or to get rid of dealing with the Optional, you can use toMap with BinaryOperator.maxBy as a merge function.
Map groupMaxOrderByType = list.stream()
.filter(Obj::isToBeAdded)
.collect(Collectors.toMap(Obj::getType, Function.identity(),
BinaryOperator.maxBy(Comparator.comparingInt(Obj::getOrder))));
List result = new ArrayList<>(groupMaxOrderByType.values());