1

I am tryning to replace that code using stream.

public int countPenaltyPointsTest(Set<OffenceEntity> offences, LocalDate date) {
    int sum = 0;
    for (OffenceEntity offence : offences) {
        for (OffenceDetailsEntity offenceDetailsEntity : offence.getDetails()) {
            if (date.isAfter(offenceDetailsEntity.getStartDate())
                    && date.isBefore(offenceDetailsEntity.getEndDate())) {
                sum +=offenceDetailsEntity.getPenaltyPoints();
            }
        }
    }

I have OffenceEntity with OneToMany relationsh with OffenceDetailsEntity:

@Entity
public class OffenceEntity {
    ...
    private Set<OffenceDetailsEntity> details;
}


@Entity
public class OffenceDetailsEntity {
    ...
    private int penaltyPoints;
    private LocalDate startDate;
    private LocalDate endDate;
    @ManyToOne
    @JoinColumn(referencedColumnName = "id")
    private OffenceEntity offence;
}

and I would like to collect penalty points where date is between two dates. I know how to do it for example when I have field like penalty points in OffenceEntity. Example:

int sum = offences.stream()
            .mapToInt(OffenceEntity::getPenaltyPoints)
            .sum();

but I do not know how to "jump" to the set in OffenceEntity

Lulex97
  • 231
  • 1
  • 10

1 Answers1

3

Since you need to flatten the stream data, i.e. turn each element of type OffenceEntity into a group of elements, mapToInt() isn't the right operation. For that purpose you can use flatMap() (or its flavor flatMapToInt()).

To differentiate between the two operations map() and flatMap(), remember a simple principle:

  • when you need one-to-one transformation, use map() or it's flavors
  • for one-to-many transformation - flatMap() or it's flavors.

Since Java 16 we can also utilize mapMulty() to flatten the data in the stream. But it's rather a special purpose tool than a common option. Apart from flattening this operation allows to filter out stream elements so in contrast to flatMap() which turns an element into 1+ (one or more) elements, mapMulty() produces 0+ (zero or more) elements.

flatMap()

flatMap() expects as an argument a function, that takes an element and produces a stream (which is also important distinction between map and flatMap).

After flattening stream data, we need to filter OffenceDetailsEntity objects having a suitable date and extract penaltyPoints. Which can be done using filter() + mapToInt().

public int countPenaltyPointsTest(Set<OffenceEntity> offences, LocalDate date) {
    
    return offences.stream()                                // Stream<OffenceEntity>
        .flatMap(offence -> offence.getDetails().stream())  // Stream<OffenceDetailsEntity>
        .filter(ode -> date.isAfter(ode.getStartDate())     // Stream<OffenceDetailsEntity>
                        && date.isBefore(ode.getEndDate()))
        .mapToInt(OffenceDetailsEntity::getPenaltyPoints)   // IntStream
        .sum();
}

mapMulty()

This operation allows incorporating imperative programming features (i.e. loops and conditional statements) into the stream pipeline.

As mentioned before, it's a special purpose tool with a lot of peculiarities which should be applied mindfully.

Here's quote from the API Note regurding when to use mapMulty():

This method is preferable to flatMap in the following circumstances:

  • When replacing each stream element with a small (possibly zero) number of elements. Using this method avoids the overhead of creating a new Stream instance for every group of result elements, as required by flatMap.
  • When it is easier to use an imperative approach for generating result elements than it is to return them in the form of a Stream.

For instance, mapMulty() is capable to substitute a combination flatMap() + filter(), or multiple flatMap() operations.

It expects an argument of type BiConsumer, i.e. a consumer, which in turn takes two arguments: stream element and a consumer of the resulting type. Each value offered to the consumer becomes a new stream element, replacing the initial element.

Here's how this method can be implemented using mapMultyToInt():

public int countPenaltyPointsTest(Set<OffenceEntity> offences, LocalDate date) {
    
    return offences.stream()
        .mapMultiToInt((offence, consumer) -> 
            offence.getDetails().forEach(ode -> {
                if (date.isAfter(ode.getStartDate()) && date.isBefore(ode.getEndDate()))
                    consumer.accept(ode.getPenaltyPoints());
        }))
        .sum();
}

For more examples and information on how to use mapMulty(), have a look at this question.

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46