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 ?

2
  • multiple declaration is not allowed, d must be final and can not be changed inside of forEach Commented Mar 14, 2018 at 8:52
  • Recently answered: stackoverflow.com/a/49249463/6099347 The same fundamentals apply even on lambdas Commented Mar 14, 2018 at 9:08

3 Answers 3

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();
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

1+, you might need to add routeLists.Strem()
@Niraj yes. I wasn't paying particular attention to the types; actually, a flatMap is needed too. Thanks for highlighting.
@Eugene fixed..
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;
 }

1 Comment

Thanks for everyone, mostly for you.
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.