Java 8 lambda within a lambda can't modify variable from outer lambda
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 Stream
s.
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.
Related videos on Youtube
Comments
-
Patrick Grimard almost 2 years
Suppose I have a
List<String>
and aList<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.
-
TylerIt might help to include the text of the compiler error.
-
Patrick GrimardError:(49, 60) java: local variables referenced from a lambda expression must be final or effectively final
-
Patrick GrimardIs there a way I can move it? Or does that require more authority than I have?
-
-
Patrick Grimard almost 10 yearsThanks, 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 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 almost 10 years@DavidConrad How does that look now?
-
David Conrad almost 10 yearsIt's definitely better without horizontal scrolling. Whether so many blank lines are better, I leave to your judgment.
-
Claude Martin almost 10 yearsIf 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.