5

I would like to use local variables in lambda functions but I get error: Please see the 1. and 2. points in the code.

class Foo {
    int d = 0; // 1. It compiles, but ugly, 2. doesnt compile
    public void findMax(List<List<Route>> routeLists) {
        int d = 0; // 2.Error : Local variable dd defined in an enclosing scope must be final or effectively final
        routeLists.forEach(e-> {
            e.forEach(ee -> {
                d+=ee.getDistance();    
            });

        });
        ... doing some other operation with d
    }
}

How can I use them whitout setting them as global variables ?

Niraj Sonawane
  • 10,225
  • 10
  • 75
  • 104
Csaba Prog
  • 53
  • 1
  • 1
  • 6

3 Answers3

8

forEach is the wrong tool for the job.

int d =
    routeLists.stream()                // Makes a Stream<List<Route>>
        .flatMap(Collection::stream)   // Makes a Stream<Route>
        .mapToInt(Route::getDistance)  // Makes an IntStream of distances
        .sum();

Or just use nested for loops:

int d = 0;
for (List<Route> rs : routeLists) {
  for (Route r : rs) {
    d += r.getDistance();
  }
}
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
2

You can't use an int as variable because it must be final to be used in a stream.

But You can create a class wrapping the int.

Then declare the variable holding this class as final.

Changing the content of the inner int variable.

public void findMax(List<List<Route>> routeLists) {
        final IntWrapper dWrapper = new IntWrapper();
        routeLists.forEach(e-> {
            e.forEach(ee -> {
                dWrapper.value += ee.getDistance();    
            });

        });

        int d = dWrapper.value;

        ... doing some other operation with d
    }

 public class IntWrapper {
    public int value;
 }
Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
1

Having side effects is discouraged (or even forbidden?) in the functions used in a stream.

A better way would be one of

routeLists.stream()
    .flatMapToInt(innerList -> innerList.stream()
        .mapToInt(Route::getDistance))
    .sum()

or

routeLists.stream()
    .mapToInt(innerList -> innerList.stream()
        .mapToInt(Route::getDistance).sum())
    .sum()

The first will, for each sublist, create a stream of distances. All these streams will become flattened and summed at once.

The second will create the sum of each sublist and then add all those sums together again.

They should be equivalent, but I am not sure if the one or other is better in terms of performance (i. e., if flattening the streams is more expensive than summing).

A third alternative of flattening the lists and then getting and summing the distances is covered by Andy Turner's answer.

glglgl
  • 89,107
  • 13
  • 149
  • 217