问题
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 Obj
s, with different types:
import java.util.Arrays;
import java.util.List;
public class Utils {
static List<Obj> createA(){
List<Obj> 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<Obj> createB(){
List<Obj> 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<Obj> createC(){
List<Obj> 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 type
s?
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<Obj> list = new ArrayList<>();
list.addAll(Utils.createA());
list.addAll(Utils.createB());
list.addAll(Utils.createC());
System.out.println(list);
List<Obj> 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.
回答1:
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<String, Optional<Obj>> 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<Obj> 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<String, Obj> groupMaxOrderByType = list.stream()
.filter(Obj::isToBeAdded)
.collect(Collectors.toMap(Obj::getType, Function.identity(),
BinaryOperator.maxBy(Comparator.comparingInt(Obj::getOrder))));
List<Obj> result = new ArrayList<>(groupMaxOrderByType.values());
来源:https://stackoverflow.com/questions/62447199/how-to-get-the-first-object-with-any-ordered-by-function-of-each-type-selecte