Let's say I have this class:
public class Thing {
private BigDecimal field1;
private BigDecimal field2;
private BigDecimal otherField1;
private BigDecimal otherField2;
private BigDecimal otherField3;
}
and in another class I for-each over a List<Thing> things
, adding field1 and field2 to a sum that I return when the iteration is done.
But what I want is to accomplish that with Java streams. The following is what I have--it works, but I feel like there has to be a way to condense it down to one stream:
public BigDecimal addFields(List<Thing> things) {
BigDecimal field1sum = things.parallelStream()
.filter(thing -> thing.getField1() != null)
.map(Thing::getField1)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal field2sum = things.parallelStream()
.filter(thing -> thing.getField2() != null)
.map(Thing::getField2)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return field1sum.add(field2sum);
}
I suspect the answer is the reduce()
method that takes three arguments, one of which is a BiFunction, but I haven't been able to figure out how to make that work. Edit: I think I could pass in (x,y) -> x.add(y)
to reduce()
, but then the question becomes how do I map()
both of those fields?
Additionally, is it possible/how would I go about turning this imperative code into a functional stream?
public BigDecimal addOtherFields(List<Thing> things) {
BigDecimal result = BigDecimal.ZERO;
for (Thing thing : things) {
if (thing.getOtherField2() != null) {
BigDecimal otherField2 = thing.getOtherField2();
otherField2 = thing.getOtherField1().subtract(otherField2);
result = result.add(otherField2);
} else if (thing.getOtherField3() != null) {
BigDecimal otherField3 = thing.getOtherField3();
otherField3 = thing.getOtherField1.subtract(otherField3);
result = result.add(otherField3);
}
}
return result;
}
Or, to be a bit more precise, how would I handle that conditional check in a stream based approach? I was trying to filter()
things out without success.
Use collect()
, with a custom collector helper, not unlike IntSummaryStatistics
.
things.stream()
.collect(ThingCollectorHelper::new,
ThingCollectorHelper::accept,
ThingCollectorHelper::combine);
Your helper class will be something like:
class ThingCollectorHelper {
BigDecimal sum1 = BigDecimal.ZERO;
BigDecimal sum2 = BigDecimal.ZERO;
void accept(Thing t) {
if (t.field1 != null)
sum1 = sum1.plus(t.field1);
if (t.field2 != null)
sum2 = sum2.plus(t.field2);
}
void combine(ThingCollectorHelper other) {
sum1 = sum1.plus(other.sum1);
sum2 = sum2.plus(other.sum2);
}
}
Since you're going to treat the fields uniformly, you may consider flatMap
:
public BigDecimal addFields(List<Thing> things) {
return things.parallelStream()
.flatMap(thing -> Stream.of(thing.getField1(), thing.getField2()))
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
public BigDecimal addOtherFields(List<Thing> things) {
return things.parallelStream()
.flatMap(thing ->
Stream.of(thing.getOtherField2(), thing.getOtherField3())
.filter(Objects::nonNull)
.map(thing.getOtherField1()::subtract)
).reduce(BigDecimal.ZERO, BigDecimal::add);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With