2

I have a stream calculating average costs the code looks something like this

private Double calculateAverageCost(List<Item> items) {
    return items.stream()
                .mapToDouble(item -> item.cost)
                .filter(cost -> cost > 0) // Ignore zero cost items
                .average()
                . // Something here to convert an OptionalDouble to value or null
}

I need the method to return the value or null if there is no value (e.g. when all costs are zero). My problem is there is no orElseNull method on OptionalDouble to do the conversion. I can do it in another step e.g.

private Double calculateAverageCost(List<Item> items) {
    final OptionalDouble average = items.stream()
                .mapToDouble(item -> item.cost)
                .filter(cost -> cost > 0) // Ignore zero cost items
                .average();
    return average.isPresent() ? average.getAsDouble() : null;
}

I realise this is a "primitive" stream and my method is returning boxed Double but it would seem like this could be helpful, similar to (Optional.empty().orElseGet(null)).

Is there a reason or a better solution im missing?

Gautham M
  • 4,816
  • 3
  • 15
  • 37
James Mudd
  • 1,816
  • 1
  • 20
  • 25
  • How about using `map` instead of `mapToDouble`? – Andy Turner May 11 '21 at 15:46
  • 3
    @AndyTurner Only the primitive stream specializations have `average()`. `Stream` doesn't have it. Do you could do it with a reduce, but it'd be uglier – Michael May 11 '21 at 15:48
  • 8
    Why not simply have the method return an `OptionalDouble`? I mean ... that is exactly why optionals were introduced. They signal the possible absence of a value after calling some method. – Seelenvirtuose May 11 '21 at 15:58

3 Answers3

13

OptionalDouble contains a primitive double. If you want an optional of a wrapped double you can just use Optional<Double>. Primitives can't be null, so orElseNull does not make sense.

As for how to convert between the two, it already has an answer here: Convert OptionalDouble to Optional <java.lang.Double>. TL;DR there is no nice, native way to do it yet. Wait for valhalla.

Naman
  • 27,789
  • 26
  • 218
  • 353
Michael
  • 41,989
  • 11
  • 82
  • 128
0

If you are returning null just to indicate that all costs are 0(or the list is empty), then you could return Double.NaN instead. This would allow your return type to be primitive double.

private double calculateAverageCost(List<Item> items) {
    return items.stream()
                .mapToDouble(item -> item.cost)
                .filter(cost -> cost > 0) // Ignore zero cost items
                .average()
                .orElse(Double.NaN);
}
double avg = calculateAverageCost(items);
if(Double.isNaN(avg)) {
    System.out.println("All costs are zero");
}

Otherwise, you could just use orElse(0d). Since you are filtering only positive costs, the average would be 0 only if all costs are 0 (or if the list is empty). So if the result is 0, then it definitely implies the absence of positive costs.

Gautham M
  • 4,816
  • 3
  • 15
  • 37
0

You can use .boxed() function of DoubleStream in your chain after mapToDouble(), that will convert your DoubleStream chain (stream of primitive) to Stream<Double> chain (stream of wrapper).

Your chain will look like:

  items.stream()
    .mapToDouble(item -> item.cost)
    .filter(cost -> cost > 0)
    .boxed() 
    .collect(Collectors.averagingDouble(Double::doubleValue)); //can't use average() function of DoubleStream
Ravi Varu
  • 61
  • 3