So, I'm almost always trying to use Streams in Java.
There are only a handful of times when Streams are not a perfect fit. And most of the times, this is when there is more than one list, and you need to traverse them in sync.
So, we have different ways of doing the same thing. Let's count 'em down.
We'll use the following test setup, using the new Java Money and Currency reference implementation1 2.
private static final CurrencyUnit euro = Monetary.getCurrency("EUR"); | |
private final List<Item> items = Arrays.asList(new Item(Money.of(2.5, euro)), | |
new Item(Money.of(2.25, euro)), | |
new Item(Money.of(5.5, euro)), | |
new Item(Money.of(10, euro)), | |
new Item(Money.of(1.25, euro)) | |
); | |
private final List<Long> amounts = Arrays.asList(4L, 10L, 2L, 1L, 45L); |
Use an ordinary for-loop
This was way back when we didn't have anything better.
And let's be honest, sometimes this is all you need and it is still surprisingly readable.
@Test | |
public void testForLoop() { | |
MonetaryAmount total = Money.of(0, euro); | |
for (int index = 0; index < items.size(); index++) { | |
total = total.add(items.get(index).getPrice().multiply(amounts.get(index))); | |
} | |
assertThat(total).hasToString("EUR 109.75"); | |
} |
Use a for-each loop
This is one of my, let us say, less perfect attempts.
It's nice that I've used the for-each construct, but too bad I've hacked an additional index to it to do what I want.
@Test | |
public void testForEachLoop() { | |
MonetaryAmount total = Money.of(0, euro); | |
int index = 0; | |
for (Item item : items) { | |
total = total.add(item.getPrice().multiply(amounts.get(index))); | |
index++; | |
} | |
assertThat(total).hasToString("EUR 109.75"); | |
} |
Streams!
So, my colleague at work provided me with this solution, to get away from the whole for-loop (at least, superficially, when you look deep deep down into it, it's just a for loop written as a stream.)
@Test | |
public void testIntStreamRange() { | |
MonetaryAmount total = IntStream.range(0, items.size()) | |
.mapToObj(index -> items.get(index).getPrice().multiply(amounts.get(index))) | |
.reduce(MonetaryAmount::add) | |
.orElse(Money.of(0, euro)); | |
assertThat(total).hasToString("EUR 109.75"); | |
} |
I has to use a reduce in the example, because one of the disadvantages here, is that it's not allowed to reassign variables inside a lambda. Variables should be effectively final.
This disadvantage is not present in for-loops.
But I've also seen some truly horrendous hacks where people started using things like AtomicInteger as a "wrapper" workaround, so they could manipulate the inside of the wrapper inside the lambda.
References
- [1] Baeldung - Java Money and the Currency API
- https://www.baeldung.com/java-money-and-currency
- [2] JSR 354: Money and Currency API
- https://jcp.org/en/jsr/detail?id=354
- Medium - Experienced Developers, Use These Quirks to Create Better Java Lambdas
- https://medium.com/javarevisited/experienced-developers-use-these-quirks-to-create-better-java-lambdas-4ae656148274
No comments:
Post a Comment