Issue
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
Solution
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 byflatMap
.- 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.
Answered By - Alexander Ivanchenko
Answer Checked By - David Marino (JavaFixing Volunteer)