Craig Otis
Craig Otis

Reputation: 32084

How do you use stream operations to calculate an average of values within a list, omitting some?

I have a method that returns the average of a property over a number of model objects:

List<Activity> activities = ...;
double effortSum = 0;
double effortCount = 0;
activities.stream().forEach(a -> {
    double effort = a.getEffort();
    if (effort != Activity.NULL) {
        effortCount++;            < Compilation error, local variable
        effortSum += effort;      < Compilation error, local variable
    }
});

But, the above attempt doesn't compile, as noted. The only solution that's coming to me is using an AtomicReference to a Double object, but that seems crufty, and adds a large amount of confusion to what should be a simple operation. (Or adding Guava and gaining AtomicDouble, but the same conclusion is reached.)

Is there a "best practice" strategy for modifying local variables using the new Java 8 loops?

Relevant code for Activity:

public class Activity {
    public static final double NULL = Double.MIN_VALUE;

    private double effort = NULL;

    public void setEffort(double effort) { this.effort = effort; }
    public double getEffort() { return this.effort; }

    ...
}

Upvotes: 2

Views: 286

Answers (2)

Alexis C.
Alexis C.

Reputation: 93872

In your case, there is a solution that is more functional - just compute the summary statistics from the stream from where you can grab the number of elements filtered and their sum:

DoubleSummaryStatistics stats = 
    activities.stream()
              .mapToDouble(Activity::getEffort)
              .filter(e -> e != Activity.NULL)
              .summaryStatistics();

long effortCount = stats.getCount();
double effortSum = stats.getSum();

Is there a "best practice" strategy for modifying local variables using the new Java 8 loops?

Don't try do to that. I think the main issues is that people try to translate their code using the new Java 8 features in an imperative way (like in your question - and then you have troubles!).

Try to see first if you can provide a solution which is functional (which is what the Stream API aim for, I believe).

Upvotes: 4

Louis Wasserman
Louis Wasserman

Reputation: 198211

Is there a "best practice" strategy for modifying local variables using the new Java 8 loops?

Yes: don't. You can modify their properties -- though it's still a bad idea -- but you cannot modify them themselves; you can only refer to variables from inside a lambda if they are final or could be final. (AtomicDouble is indeed one solution, another is a double[1] that just serves as a holder.)

The correct way of implementing the "average" operation here is

activities.stream()
    .mapToDouble(Activity::getEffort)
    .filter(effort -> effort != Activity.NULL)
    .average()
    .getAsDouble();

Upvotes: 6

Related Questions