Группировка Java 8 в коллекцию объектов

Я хотел бы сгруппировать коллекцию объектов Rental в установленный срок, но я хочу создать новый объект RentalReport для каждой группы с ключом в качестве предопределенного значения (перечисление) и группой в качестве свойства этого объекта. Я добился этого, подогнав коллекцию по каждому критерию и создав объект RentalReport для каждого, но мне было интересно, можно ли это сделать с помощью метода groupingBy класса Collectors.

Можно ли сгруппировать с помощью предопределенного набора фильтров в java 8, чтобы я мог создать карту, где ключ - это перечисление, а значение - это коллекция объектов Rental. Затем я мог бы перебрать эту карту и сгенерировать объекты RentalReport.

Я создал эту демонстрацию, но настоящая задача включает в себя несколько предложений group by, поэтому было бы здорово, если бы я смог добиться этого путем группировки.

    import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.joda.time.DateTime;

public class TestGrouping {

    enum RentalClassification {
        UNRESTRICTED, //No restriction restricted=false
        OVERDUE,      //todays date after due date.
        NEARLY_OVERDUE, // todays date after due date + 2 days
        NOT_OVERDUE
    }

    public static void main(String[] args) {

        class Rental {
            Integer rentalId;
            Integer productId;
            boolean restricted;
            DateTime dueDate;

            public Rental(Integer rentalId, Integer productId, boolean restricted, DateTime dueDate){
                this.rentalId = rentalId;
                this.productId = productId;
                this.restricted = restricted;
                this.dueDate = dueDate;
            }

            public Integer getRentalId() {
                return rentalId;
            }

            public void setRentalId(Integer rentalId) {
                this.rentalId = rentalId;
            }

            public Integer getProductId() {
                return productId;
            }

            public void setProductId(Integer productId) {
                this.productId = productId;
            }

            public boolean isRestricted() {
                return restricted;
            }

            public void setRestricted(boolean restricted) {
                this.restricted = restricted;
            }

            public DateTime getDueDate() {
                return dueDate;
            }

            public void setDueDate(DateTime dueDate) {
                this.dueDate = dueDate;
            }

            public String toString(){
                return "RentalId:"+this.rentalId+". ProductId:"+this.productId+". Due date:"+this.dueDate+". -";
            }
        }

        class RentalReport {

            RentalClassification classification;
            List<Rental> rentals;

            public RentalReport(RentalClassification classification, List<Rental> rentals) {
                this.classification = classification;
                this.rentals = rentals;
            }

            public RentalClassification getClassification() {
                return classification;
            }
            public void setClassification(RentalClassification classification) {
                this.classification = classification;
            }
            public List<Rental> getRentals() {
                return rentals;
            }
            public void setRentals(List<Rental> rentals) {
                this.rentals = rentals;
            }

            public String toString(){
                StringBuilder sb = new StringBuilder("Classification:"+this.classification.name()+". Rental Ids:");
                this.rentals.forEach(r -> sb.append(r.getRentalId()));
                return sb.toString();

            }

        }

        DateTime today = new DateTime();

        List<Rental> rentals = Arrays.asList(
                new Rental(1,100, true, today.plusDays(-10)),
                new Rental(2,101, false, today.plusDays(-10)),
                new Rental(3,102, true, today.plusDays(-4)),
                new Rental(4,103, true, today.plusDays(-4)),
                new Rental(5,104, true, today.plusDays(-4)),
                new Rental(6,105, true, today.plusDays(2)),
                new Rental(7,106, true, today.plusDays(2)),
                new Rental(8,107, true, today.plusDays(2)),
                new Rental(9,108, true, today.plusDays(4)),
                new Rental(10,109, true, today.plusDays(5))
                );


        List<RentalReport> rentalReports = new ArrayList<RentalReport>();

        List<Rental> unrestrictedRentals = rentals.stream()
                                           .filter(r -> !r.isRestricted())
                                           .collect(Collectors.toList());

        rentalReports.add(new RentalReport(RentalClassification.UNRESTRICTED, unrestrictedRentals));

        List<Rental> overdueRentals = rentals.stream()
                                      .filter(r -> r.isRestricted() && r.getDueDate().isBefore(new DateTime()))
                                      .collect(Collectors.toList());

        rentalReports.add(new RentalReport(RentalClassification.OVERDUE, overdueRentals));

        List<Rental> nearlyOverdueRentals = rentals.stream()
                                            .filter(r -> r.isRestricted() 
                                                    && r.getDueDate().isAfter(new DateTime())
                                                    && r.getDueDate().isBefore(new DateTime().plusDays(2)))
                                            .collect(Collectors.toList());

        rentalReports.add(new RentalReport(RentalClassification.NEARLY_OVERDUE, nearlyOverdueRentals));

        List<Rental> notOverdueRentals = rentals.stream()
                                        .filter(r -> r.isRestricted() && r.getDueDate().isAfter(new DateTime()))
                                        .collect(Collectors.toList());

        rentalReports.add(new RentalReport(RentalClassification.NOT_OVERDUE, notOverdueRentals));

        System.out.println("Rental Reports: "+rentalReports.toString());

    }
    }
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
0
836
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Как насчет такого:

rentals.stream().collect(Collectors.groupingBy(r -> {
    if (!r.isRestricted()) {
        return RentalClassification.UNRESTRICTED;
    }
    if (r.isRestricted() && r.getDueDate().isBefore(new DateTime())) {
        return RentalClassification.OVERDUE;
    }
    // and so on
}));

Возможно, стоит добавить эту лямбду в Rental как метод RentalClassification getRentalClassification().

Вот подход:

List<RentalClassification,List<Rental>> = rentals
                                            .stream()
                                            .map(r -> getClassifiedRental(r))
                                            .collect(Collectors.groupingBy(SimpleEntry::getKey,Collectors.mapping(SimpleEntry::getValue,Collectors.toList())))

весь фокус в методе getClassifiedRental(Rental r):

SimpleEntry<RentalClassification,Rental> getClassifiedRental(Rental r){

    if (r -> !r.isRestricted())
        return new SimpleEntry<RentalClassification,Rental>(RentalClassification.UNRESTRICTED,r);

    if (r -> r.isRestricted() && r.getDueDate().isBefore(new DateTime()))

        return new SimpleEntry<RentalClassification,Rental>(RentalClassification.OVERDUE,r);

    if (r -> r.isRestricted() 
            && r.getDueDate().isAfter(new DateTime())
            && r.getDueDate().isBefore(new DateTime().plusDays(2)))
        return new SimpleEntry<RentalClassification,Rental>(RentalClassification.NEARLY_OVERDUE,r);

    if (r -> r.isRestricted() && r.getDueDate().isAfter(new DateTime()))
        return new SimpleEntry<RentalClassification,Rental>(RentalClassification.NOT_OVERDUE,r);

}

с SimpleEntry - это кастомная реализация интерфейса Map.Entry (вам придется написать ее самостоятельно):

public class SimpleEntry implements Entry<RentalClassification, Rental> {
// implementation
}

наличие getClassifiedRental(Rental r) поможет вам разделить рутину классификации аренды для лучшего, например, тестирования, рефакторинга, например

Вот один из подходов к groupingBy.

Function<Rental, RentalClassification> classifyRental = r -> {
    if (!r.isRestricted())
        return RentalClassification.UNRESTRICTED;
    else if (r.getDueDate().isBefore(new DateTime()))
        return RentalClassification.OVERDUE;
    else if (r.getDueDate().isAfter(new DateTime())
            && r.getDueDate().isBefore(new DateTime().plusDays(2)))
        return RentalClassification.NEARLY_OVERDUE;
    else
        return RentalClassification.NOT_OVERDUE;
};
Map<RentalClassification, List<Rental>> rentalReportMap = rentals.stream()
        .collect(groupingBy(classifyRental));
rentalReportMap
        .forEach((classification, rental) -> rentalReports.add(new RentalReport(classification, rental)));

Очевидно, что между вашими 4 константами enum RentalClassification существует взаимно однозначная связь. и 4 тестовых лямбда-выражения. Поэтому имеет смысл интегрировать каждое лямбда-выражение в соответствующую константу enum. Каждая константа enum будет содержать свое тестовое лямбда-выражение в виде Predicate<Rental>. Тип enum будет иметь метод boolean test(Rental), выполняющий тест. с собственным Predicate.
И пока мы находимся в этом: из-за метода test мы можем добавить implements Predicate<Rental> к типу enum. Это окажется полезным в конце.

enum RentalClassification implements Predicate<Rental> {

    UNRESTRICTED(    //No restriction restricted=false
            r -> !r.isRestricted()),
    OVERDUE(         //todays date after due date.
            r -> r.isRestricted()
            && r.getDueDate().isBefore(new DateTime())),
    NEARLY_OVERDUE( // todays date after due date + 2 days
            r -> r.isRestricted() 
            && r.getDueDate().isAfter(new DateTime())
            && r.getDueDate().isBefore(new DateTime().plusDays(2))),
    NOT_OVERDUE(
            r -> r.isRestricted()
            && r.getDueDate().isAfter(new DateTime()));

    private RentalClassification(Predicate<Rental> predicate) {
        this.predicate = predicate;
    }

    private Predicate<Rental> predicate;

    @Override
    public boolean test(Rental r) {
       return predicate.test(r);
    }
}

Используя этот улучшенный тип enum, вы можете написать простой метод классификации Rental. Он будет перебирать все константы RentalClassification пока он не найдет тот, где тест завершится успешно:

static RentalClassification classify(Rental rental) {
   for (RentalClassification classification : RentalClassification.values()) {
       if (classification.test(rental))
           return classification;
   }
   return null;  // should not happen
}

Используя этот метод классификации, вы можете легко создать желаемую карту:

Map<RentalClassification, List<Rental>> map =
        rentals.stream()
               .collect(Collectors.groupingBy(r -> classify(r)));

В качестве альтернативы возможен несколько иной подход. Он использует константы enum непосредственно в качестве предикатов фильтрации для получения нескольких списков:

List<Rental> unrestrictedRentals =
        rentals.stream()
               .filter(RentalClassification.UNRESTRICTED)
               .collect(Collectors.toList());
List<Rental> overdueRentals =
        rentals.stream()
               .filter(RentalClassification.OVERDUE)
               .collect(Collectors.toList());
// ...

это приятно, спасибо. Вот как я буду реализовывать свой проект. :)

jonesy 01.05.2018 21:29

Другие вопросы по теме