5

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.

4
  • 1
    It might help to include the text of the compiler error. Commented May 3, 2014 at 23:50
  • 1
    Error:(49, 60) java: local variables referenced from a lambda expression must be final or effectively final Commented May 4, 2014 at 0:03
  • This question is very good; however, I would suggest that it should be moved to Stack Overflow. Commented May 4, 2014 at 0:27
  • 1
    Is there a way I can move it? Or does that require more authority than I have? Commented May 4, 2014 at 1:00

2 Answers 2

13

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.

Sign up to request clarification or add additional context in comments.

5 Comments

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 :)
+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.
@DavidConrad How does that look now?
It's definitely better without horizontal scrolling. Whether so many blank lines are better, I leave to your judgment.
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.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.