Java 8 лямбда внутри лямбда не может изменять переменную из внешней лямбда

Предположим, что у меня есть List<String> и a List<Transfomer>. Я хочу применить каждый трансформатор к каждой строке в списке.

Используя Java 8 lambdas, я могу это сделать:

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

Но я хотел бы сделать что-то более похожее, но это приводит к ошибке времени компиляции:

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

Я только начинаю играть с лямбдами, так что, возможно, у меня просто нет синтаксиса правильно.

Ответ 1

Лучший способ сделать это с помощью потоков - использовать 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);

Конечно, это предполагает, что transformers непусто; если есть вероятность, что он пуст, то достаточно просто использовать перегрузку с двумя аргументами reduce вместо этого, например, (это предполагает, что Tranformer является функциональным интерфейсом):

// 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);

Причина, по которой вы получили ошибку компилятора, заключается в том, что, как говорит ошибка, внешние переменные, используемые в выражении лямбда, должны быть эффективно окончательными; то есть объявить их final (если они еще не установлены) не должны изменять значение программы или изменять, компилируется она или нет. Поэтому использование измененного назначения в лямбда, как правило, запрещено, и с полным основанием: мутация прикручивает распараллеливание, и одна из основных причин, которые lambdas были включены в Java 8, заключалась в том, чтобы упростить параллельное программирование.

Вообще говоря, всякий раз, когда вы хотите каким-то образом "подвести итоги", reduce (в любой из трех своих перегрузок) - это ваш метод go-to. Изучение того, как использовать map, filter, reduce и flatMap, очень важно при работе с Stream s.

Ответ 2

Lambdas (как и локальные классы) никогда не может назначать захваченные локальные переменные, будь то из внешней лямбда или из закрывающего метода. Захваченные локальные переменные должны быть окончательно окончательными.