2

I am trying to calculate the minimum between some values using lambdas. These values may contain nulls which is normal for my business case, as they represent interval limits. For example if I have the next intervals (null, 10], (10, 15], [20, null) - I want to check which is the lowest interval boundary between values (null, 10, 20) and I expect null to be the result (as null should be considered the equivalent of –infinity as a business case rule).

I tried implementing this with streams but I noticed that the min method from the Stream class cannot be used for such a scenario.

private Integer lowestIntervalLimit(Integer... intervalsLowerLimits) {
    return Arrays.stream(intervalsStart)
            .min(nullsFirst(Integer::compareTo))
            .orElse(null);
}

The reason is described in the min method Javadoc “Throws: NullPointerException - if the minimum element is null”. I tried a workaround and used reduce instead of min but I have the exact same issue:

private Integer lowestIntervalLimit(Integer... intervalsLowerLimits) {
    return Arrays.stream(intervalsStart)
            .reduce(BinaryOperator.minBy(nullsFirst(Integer::compareTo)))
            .orElse(null);
}

Am I missing something here or is this an API limitation and I should stick to fors and ifs?

shmosel
  • 49,289
  • 6
  • 73
  • 138
semi
  • 118
  • 2
  • 8
  • 2
    As an idea, could you `map` the collection first and set any `null` values to `Integer.MIN_VALUE`, and then continue? – Karl Reid Jan 30 '18 at 17:51
  • 3
    You could just sort intervals rather than lower bounds, or use Integer.MIN_VALUE, or use Optional, or wrap the Integer into a Bound object. min() returns an Optional. An Optional can't hold a null value. And if it returned an empty Optional, the result would be ambiguous: does it mean there is no min value, or that the min value is null? – JB Nizet Jan 30 '18 at 17:58
  • JB Nizet is right, the API is like this due to the ambiguous situation between empty lists (no min value) and cases when the min value is null. Sorry, it’s late in this time zone, I should have seen that :) Thanks! – semi Jan 30 '18 at 18:13

1 Answers1

1

Not being able to return a null result, is connected to the Optional, see also, “Why does findFirst() throw a NullPointerException if the first element it finds is null?”.

Since your input is a plain array, there is no reason to worry about iterating it twice, e.g.

private Integer lowestIntervalLimit(Integer... intervalsLowerLimits) {
    return Arrays.stream(intervalsLowerLimits).anyMatch(Objects::isNull)? null:
        Arrays.stream(intervalsLowerLimits).min(Comparator.naturalOrder()).orElse(null);
}

But if that’s a concern, you could just use a loop

private Integer lowestIntervalLimit(Integer... intervalsLowerLimits) {
    Integer i = null;
    for(Integer j: intervalsLowerLimits)
        if(j == null) return null; else if(i==null || j<i) i = j;
    return i;
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I can't tell what has been asked more, special null handling or order in streams... good answer, yet again! – Eugene Jan 31 '18 at 08:43