0

Below is the simplified structure of my classes:

public class Thing {
    private BigDecimal field1;
    private BigDecimal field2;
    private XYZ field3;
    private BigDecimal field4;
}

public class XYZ {
    private BigDecimal field5;
    private BigDecimal field6;
}

I need to sum up the following fields field1, field2 and field3.field5 (i.e. property field5 of the XYZ object). All of them can be null including field3.

I've implemented this logic in the way shown below, but it lucks the null-check field3, how can it be implemented?

My code:

public BigDecimal addFields(List<Thing> things) {
    return things.parallelStream()
        .flatMap(thing -> Stream.of(thing.getField1(),
                                    thing.getField2(),
                                    thing.getField3().getField5()))
        .reduce(BigDecimal.ZERO, BigDecimal::add);
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
Ankur Goel
  • 99
  • 1
  • 1
  • 7
  • What null logic on required fields do you mean? – Arsegg Jul 13 '22 at 13:18
  • @Arsegg : required fields means field1, field2 and field 5 only. If field 1 is null or empty then just add field 2 and field 5 like that. – Ankur Goel Jul 13 '22 at 13:28
  • @AnkurGoel `thing = new Thing(null,null,new XYZ(null, null),null);` even this will produce `0` as output. The only exception I can think of is when XYZ is null i.e `Thing(null,null,null,null)` – Sayan Bhattacharya Jul 13 '22 at 13:30
  • @SayanBhattacharya : that is my actual use case having many objects like XYZ in main class. – Ankur Goel Jul 13 '22 at 13:34
  • I feel like the problem has been _overly_ reduced, to the point of not really making sense. As it stands your stream is already performing `.filter(Objects::nonNull)`, such that anything that reaches the `Stream#reduce` call will not be null. What do these `field#` fields actually represent? – Rogue Jul 13 '22 at 13:43
  • @Rogue : i want to avoid null pointer in flatmap operation and i think there is no point of adding .filter(Objects::nonNull) after flatMap. – Ankur Goel Jul 13 '22 at 13:54

1 Answers1

3

I want to avoid null pointer in flatmap operation.

In case if XYZ object can be null you can use an explicit null-check, for instance inside the ternary operator. Or with Java 9 + you can use Stream.ofNullable().

public BigDecimal addFields(List<Thing> things) {
    return things.parallelStream()
        .flatMap(thing -> Stream.concat(
            Stream.of(thing.getField1(), thing.getField2()),
            Stream.ofNullable(thing.getField3()).map(XYZ::getField5)))
        .filter(Objects::nonNull)
        .reduce(BigDecimal.ZERO, BigDecimal::add);
}
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • Is this efficient in java8 `.flatMap(thing ->{ BigDecimal num = thing.getField3()==null? new BigDecimal(0):thing.getField3().getField5(); return Stream.of(thing.getField1(), thing.getField2(), num); })` or is there other method like `Stream.ofNullable` – Sayan Bhattacharya Jul 13 '22 at 14:04
  • 1
    @SayanBhattacharya There's nothing wrong with performing an explicit null-check. You've missed the return statement, which is mandatory for multiline lambda. – Alexander Ivanchenko Jul 13 '22 at 14:06
  • @AlexanderIvanchenko what if i have another object in XYZ and that i also need to check like : `.flatMap(installment -> Stream.of(installment.getPrincipal().getAmount().getDue(), installment.getInterest().getAmount().getDue() , installment.getInterest().getTax().getDue(), installment.getFee().getAmount().getDue() ))` – Ankur Goel Jul 13 '22 at 14:07
  • @AnkurGoel Well, that's an old story. OK, there's nothing wrong with null-checks and `null` is also a valid value, but the problem appears when you have a lot of spots which require defensive null-checking, that's a smell. – Alexander Ivanchenko Jul 13 '22 at 14:12
  • @AnkurGoel You want to blind on all the `null` objects without throwing? – Alexander Ivanchenko Jul 13 '22 at 14:13
  • @AlexanderIvanchenko i want to avoid null pointer as well as don't want to consider fields having null value for sum. – Ankur Goel Jul 13 '22 at 14:16
  • @AnkurGoel You might consider implementing the *Null object pattern* for `Interest` and `Fee`. And *null-object* would peacefully return `BigDecimal.ZERO` when you invoke `.getAmount().getDue()` without additional ceremony. – Alexander Ivanchenko Jul 13 '22 at 14:29
  • 1
    Well, yes, you can replace absent values with `BigDecimal.ZERO`, but I still don’t understand the OP’s irrational aversion of `.filter(Objects::nonNull)`. If they want avoid `null`, they should avoid it at the source already, i.e. never let `getDue()` return `null`, rather than in the stream operation. – Holger Jul 13 '22 at 15:06
  • @Holger My bad, you're right, this workaround would not work at. If `Interest` and `Fee` are immutable, I guess a possible solution would create a *null-object* which would be assigned to `installment` by default. – Alexander Ivanchenko Jul 13 '22 at 15:19
  • @AnkurGoel And the workaround for cases when `principel`, `interest`, etc. can be `null` would be the following: `.flatMap(installment -> Stream.of( installment.getPrincipal() == null ? null : installment.getPrincipal().getAmount().getDue(), installment.getInterest() == null ? null : installment.getInterest().getAmount().getDue(), installment.getInterest() == null ? null : installment.getInterest().getTax().getDue(), installment.getFee() == null ? null : installment.getFee().getAmount().getDue() ))` . – Alexander Ivanchenko Jul 13 '22 at 15:25
  • I guess in this case, there's no possibility to fix something on the spot, without defeating the readability of the stream. It's a design problem rather than an issue which can be addressed in the scope of a single method. – Alexander Ivanchenko Jul 13 '22 at 15:29
  • @AlexanderIvanchenko : installment.getInterest().getAmount() can also be null, we need to take care of that also. Can you guide me with null-object approach. We are calling third party api and they are returning response in this structure and we are integration team in between that need to do calculation and return final response. – Ankur Goel Jul 13 '22 at 15:33
  • @AnkurGoel Get familiar with the basic descriptions https://en.wikipedia.org/wiki/Null_object_pattern https://www.baeldung.com/java-null-object-pattern and try to apply. Also, you might review your data flow and consider refining the way you're constructing these objects (to avoid having an object tree packed with `null`s, maybe there's a way only to keep the root to be `null`). – Alexander Ivanchenko Jul 13 '22 at 15:43
  • @AnkurGoel When you have an object graph where you can encounter `null` on multiple levels, firstly think about truncating these levels of nested nullable object (meaning if a particular property is `null` can the object still deliver useful information, if not - seems like we don't need it). You can not apply *null object pattern* everywhere, it would be too cumbersome, try to combine different approaches. – Alexander Ivanchenko Jul 13 '22 at 16:29