Как получить первый объект (с любой упорядоченной функцией) каждого типа (выбранный по атрибуту) в Java Stream - PullRequest
2 голосов
/ 18 июня 2020

Представьте себе простой объект с 3 атрибутами:

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

}

Представьте, что у меня есть список из нескольких Obj s разных типов:

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

Я хочу как-то отфильтровать этот список и иметь последнее Obj (максимальное значение order) для каждого type, которое может быть добавлено (toBeAdded = true).

В этом примере результатом должен быть список с:

  • Obj1: тип 1, порядок 3
  • Obj2: тип 2, порядок 3
  • Obj3: тип 3, порядок 4

Я знаю, как фильтровать и сортировать, но до сих пор не мог понять, как получить первое в каждом подтипе. Это противоречит правилам Stream? Поскольку то, что я хочу сделать, это в основном проанализировать N разных подпотоков для N разных type s?

Вот что я мог сделать до сих пор:

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

    }


}

Однако это всего лишь делать простую часть - фильтровать и упорядочивать. Мне все еще нужно сделать что-то вроде findFirst() для каждого type. Есть ли способ сделать это?

Нужно ли мне создавать 3 разных потока (по одному для каждого типа), а затем объединять списки? Есть ли способ сделать это, не зная, сколько типов у нас будет? Я также читал про collect(Collectors.groupingBy()), но это создаст разные карты для каждого типа, что мне не нужно.

1 Ответ

5 голосов
/ 18 июня 2020

Использование groupingBy может помочь здесь вместе с последующим minBy / maxBy. Это предоставит карту для поиска, если значение после фильтрации присутствует для каждого типа.

Map<String, Optional<Obj>> groupMaxOrderByType = list.stream()
        .filter(Obj::isToBeAdded)
        .collect(Collectors.groupingBy(Obj::getType,
                Collectors.maxBy(Comparator.comparingInt(Obj::getOrder)))); //highest order

Поиск или доступ к этим объектам преобразуются во что-то вроде:

Obj maxPerTypeA = groupMaxOrderByType.get("A").orElse.. // similar for each type

Редактировать : Или, если вы собираете все такие существующие типы в окончательный результат, вы можете следовать, чтобы получить доступ к значениям Map.

List<Obj> result = groupMaxOrderByType.values().stream()
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(Collectors.toList());

Edit : Или, чтобы избавиться от работы с Optional, вы можете использовать toMap с BinaryOperator.maxBy в качестве функции слияния.

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());
...