How to get the first object (with any ordered by function) of each type (selected by attribute) in Java Stream

大城市里の小女人 提交于 2021-02-04 16:32:25

问题


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<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 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<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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!