Java 8 lambda within a lambda can't modify variable from outer lambda

10,777

Solution 1

The best way to do this with streams is to use reduce:

// make a transformer that combines all of them as one
Transformer combinedTransformer =

    // the stream of transformers
    transformers.stream()

    // combine all the transformers into one
    .reduce(

        // apply each of the transformers in turn
        (t1, t2) -> x -> t2.apply(t1.apply(x)))

    );



// the stream of strings
strings.stream()

// transform each string with the combined transformer
.map(combinedTranformer::apply);

Of course, this assumes that transformers is non-empty; if there is a possibility that it is empty, than it is simple enough to use the two-argument overload of reduce instead, like so (this assumes Tranformer is a functional interface):

// make a transformer that combines all of them as one
Transformer combinedTransformer =

    // the stream of transformers
    transformers.stream()

    // combine all the transformers into one
    .reduce(

        // the no-op transformer
        x -> x,

        // apply each of the transformers in turn
        (t1, t2) -> x -> t2.apply(t1.apply(x)))

    );



// the stream of strings
strings.stream()

// transform each string with the combined transformer
.map(combinedTranformer::apply);

The reason you got a compiler error is that, as the error says, outside variables used in a lambda expression must be effectively final; that is, declaring them final (if they aren't already) must not change the meaning of the program, or change whether or not it compiles. Using a mutable assignment in a lambda is therefore generally forbidden, and with good reason: mutation screws up parallelization, and one of the major reasons lambdas were included in Java 8 was to allow easier parallel programming.

Generally speaking, whenever you want to "sum up" results in some way, reduce (in any of its three overloads) is your go-to method. Learning how to use map, filter, reduce, and flatMap effectively is very important when working with Streams.

Solution 2

Lambdas (just like local classes) cannot ever assign to captured local variables, whether from an outer lambda, or from an enclosing method. Captured local variables must be effectively final.

Share:
10,777

Related videos on Youtube

Patrick Grimard
Author by

Patrick Grimard

Hobbyist Coder.

Updated on June 04, 2022

Comments

  • Patrick Grimard
    Patrick Grimard almost 2 years

    Suppose I have a List<String> and a List<Transfomer>. I want to apply each transformer to each string in the list.

    Using Java 8 lambdas, I can do this:

    strings.stream().map(s -> {
        for(Transformer t : transformers) {
            s = t.apply(s);
        }
        return s;
    }).forEach(System.out::println);
    

    But I'd like to do something more like this, however it results in a compile time error:

    strings.stream().map(s -> transformers.stream().forEach(t -> s = t.apply(s))).forEach(System.out::println);
    

    I'm just starting to play with lambdas, so maybe I just don't have the syntax correctly.

    • Tyler
      Tyler
      It might help to include the text of the compiler error.
    • Patrick Grimard
      Patrick Grimard
      Error:(49, 60) java: local variables referenced from a lambda expression must be final or effectively final
    • Patrick Grimard
      Patrick Grimard
      Is there a way I can move it? Or does that require more authority than I have?
  • Patrick Grimard
    Patrick Grimard almost 10 years
    Thanks, that's quite a bit to take in with my limited experience using lambdas. The syntax is a little confusing, but I'll go through it until I understand it :)
  • David Conrad
    David Conrad almost 10 years
    +1, but I'd find it easier to read the code if the comments were shorter or on separate lines so that there wasn't horizontal scrolling.
  • Ptharien's Flame
    Ptharien's Flame almost 10 years
    @DavidConrad How does that look now?
  • David Conrad
    David Conrad almost 10 years
    It's definitely better without horizontal scrolling. Whether so many blank lines are better, I leave to your judgment.
  • Claude Martin
    Claude Martin almost 10 years
    If you use the predefined UnaryOperator instead of Transformer you can use UnaryOperator.identity() instead of x->x. It always returns the same object while x->x unnecessarily creates a new lambda object.