2

I have a Java 8 stream expression that has 3 filters and works fine. I want to guard against null pointer exceptions within the filters for most of the values. This is the expression:

if(!purchasedTripSegments.isEmpty()) {
    filteredList = purchasedTripSegments.stream()
        .filter(segment -> PurchasedVendorType.RAIL.equals(segment.getVendorType()))
        .filter(distinctByKeys(segment -> Arrays.asList(segment.getBillingMethod(), 
            segment.getOrigin().getNumberCode(), segment.getDestination().getNumberCode(), 
            segment.getStopOff().getStopOffLocation().getNumberCode()))) 
        .filter(segment -> segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_LOCAL) ||
               (segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_RULE) &&
                segment.getDestination().getNumberCode() != 
                  segment.getStopOff().getStopOffLocation().getNumberCode())) 
        .collect(Collectors.toList());
}

So the VendorType cannot be null. So the first filter will be fine. The 2nd and 3rd filters can have nulls. The objects (Origin, Destination, StopOff, StopOffLocation) can be null. And the values (BillingMethod, NumberCode) can be null.

Is there a way to ignore the filter if any of the values in the filter are nulls?

I tried adding .filter(Objects::nonNull) I have a test case that has a null destination object and the NullPointerException is thrown.

UPDATE I updated the billingMethod. But I am not clear on how to use Optional to avoid the null checks.

Optional<List<PurchasedTripSegment>> filteredList =  Optional.ofNullable(new ArrayList<>());

if(!purchasedTripSegments.isEmpty()) {
    filteredList = purchasedTripSegments.stream()
     .filter(segment -> PurchasedVendorType.RAIL.equals(segment.getVendorType()))
     .filter(distinctByKeys(segment -> Arrays.asList(segment.getBillingMethod(), 
                            segment.getOrigin().getNumberCode(), 
                            segment.getDestination().getNumberCode(), 
                    segment.getStopOff().getStopOffLocation().getNumberCode()))) 
     .filter(segment -> BILLING_METHOD_LOCAL.equals(segment.getBillingMethod()) 
                  || (BILLING_METHOD_RULE.equals(segment.getBillingMethod()) &&
                               segment.getDestination().getNumberCode() != 
                      segment.getStopOff().getStopOffLocation().getNumberCode())) 
      .collect(Collectors.toList());
}

I'm not sure how to apply the changes you suggested to my filter. I tried adding as written but the map() was not recognized. The middle filter would be the most difficult. How to check the objects and values for each segment?

UPDATE As per the comment below implementing a Utility method using Optional.

private Optional<Integer> getDestinationCode(PurchasedCostTripSegment purchasedCostTripSegment) {
        return Optional.ofNullable(purchasedCostTripSegment.getDestination()) // empty for 'null'
                .map(Destination::getNumberCode);
    }

I do a null check for the incoming parameter. I get an error that the method getNumberCode is not recognized.

2 Answers 2

1

The attributes such as the billingMethod, whenever it is possibly null inside the List, it should still work for comparison to get distinct values. On the other hand, comparing them with some other String constant can be solved in the manner the user FilipRistic suggested.

But, when it is about objects which could be possibly null and you want to access the inner attributes further down safely, you can make use of Optional and chain the accessors. For a sample amongst those, while you want to access the numberCode of your destination which could possibly be null, you can have an accessor in PurchasedTripSegment class to expose this:

Optional<Integer> getDestinationCode() {
    return Optional.ofNullable(this.getDestination()) // empty for 'null'
                   .map(Node::getNumberCode);
}

With similar changes for other accessors, your overall code would update and change to something like:

filteredList = purchasedTripSegments.stream()
        .filter(segment -> PurchasedVendorType.RAIL.equals(segment.getVendorType()))
        .filter(distinctByKey(segment -> Arrays.asList(segment.getBillingMethod(),
                segment.getOriginCode(), segment.getDestinationCode(),
                segment.getStopOffLocationCode())))
        .filter(segment -> segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_LOCAL) ||
                (segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_RULE) &&
                        segment.getDestinationCode().equals(segment.getStopOffLocationCode())))
        .collect(Collectors.toList());
Sign up to request clarification or add additional context in comments.

10 Comments

Thank you for that explanation! Let me work on that.
But one question...why is the getNumberCode of type Node?
@GloriaSantin I just mimicked the class for Destination and named it Node, you can replace it with Destination if that's your class name. The type of numberCode is still Integer and that's just a map operation.
I don't have access to the PurchaseTripSegment class or Location class that is Destination. So, I am created a method in the Utility Class that I need this functionality. I pass the PurchaseTripSegment in as a parameter. To add the Optional functionality into the Location class for the getNumberCode method to work, would i add another Optional.ofNullable to the getDestinationCode method?
@GloriaSantin If its a utility method that you are introducing then it might look something like Optional<Integer> getDestinationCode(PurchaseTripSegments segment) { return Optional.ofNullable(segment.getDestination()) // empty for 'null' .map(Node::getNumberCode); }. This of course, relies on the segment being Nonnull, other wise Optional.ofNullable(segment).map().map() could bean alternate.
|
1

No there isn't any way for filter to know that since it doesn't know in which way you will use element inside Predicate, your only solution is to perform the check for null yourself.

Note that you can avoid check in cases where you are comparing to constant that you know isn't null, instead of writing:

segment.getBillingMethod().equalsIgnoreCase(BILLING_METHOD_LOCAL)

You could write it like this:

BILLING_METHOD_LOCAL.equalsIgnoreCase(segment.getBillingMethod())

This will avoid NPE but it only helps you in few cases not all of them, for other cases you will have to perform check or maybe refactor to return type Optional and your condition could look something like this:

segment.getDestination()
    .flatMap(d -> segment.getStopOff()
        .map(s -> s.getStopOffLocation)
        .filter(s -> s.getNumberCode() == d.getNumberCode()) )
    .isPresent();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.