2

I have a map which contains a map. Map> For all entries in the map, I want to calculate the sum of a particular key.

For example my map is something like this:

Key1    Key2    Value
A       Z       10.10
B       Z       40.10
C       Y       20.10

I want to calculate basically the sum of all the key2 which is equal to B. So in this case I want to get 50.20 as Key1 -C does not have key2 Z

I am trying to do this using Java 8. I am not sure how I should collect the sum.

double sum = 0;
myMap.forEach((key1, key2) -> {
    sum += key2.get("Z");
});

But then I get an error saying that value inside lambda should be a final.

Grzegorz Piwowarek
  • 13,172
  • 8
  • 62
  • 93
user1692342
  • 5,007
  • 11
  • 69
  • 128
  • 5
    `map.values().stream().mapToDouble(x -> x.get("Z")).sum()` – Misha Aug 22 '17 at 02:58
  • The error you are getting is expected. If you want to access `sum` from within a lambda, it needs to be final. You can't get a reference to anything that isn't final from within a lambda. – Tavo Aug 22 '17 at 03:03

4 Answers4

3

All external variables used within the anonymous inner class or Lambda need to be final or effectively final(a non-final variable that is never reassigned).

In your solution, you are trying to fix classical imperative solution with a functional one.

An idiomatic Java-8 approach would be to use Stream API:

map.values().stream()
  .map(x -> x.get("Z"))
  .reduce(0, Double::sum);

or utilize the specialized Stream for doubles:

map.values().stream()
  .mapToDouble(x -> x.get("Z"))
  .sum()

Remember to properly handle edge cases. This will explode if there is no value associated with the "Z" key.

Grzegorz Piwowarek
  • 13,172
  • 8
  • 62
  • 93
1

You could use a Stream. That way you could use intermediate operations, too:

myMap.entrySet().stream()
                .filter(entry -> entry.getValue().equals(Z))
                .map(entry -> entry.getValue())
                .mapToDouble(v -> v.get("Z"))
                .sum()

I am not sure about your data structure, so this might need a little work, but I hope you get the idea.

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html

Your approach does not work, because you try to modify a local variable in a scope where it can't be modified. See http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html

tl;dr You can not modify local variables in a lambda body.

Felix S
  • 108
  • 9
1

Or you can use AtomicInteger and it's threadSafe

avijendr
  • 3,958
  • 2
  • 31
  • 46
0

You got your answer how to do it correctly (your example) with streams. Sometimes that is not feasible though (even inside jdk sources there are places where an array wrapper is needed):

double [] sum = {0};
myMap.forEach((key1, key2) -> {
   sum[0] += key2.get("Z");
});
Eugene
  • 117,005
  • 15
  • 201
  • 306