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

Предположим, у меня есть List<String> и 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);

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

3 ответа

Решение

Лучший способ сделать это с потоками - это использовать 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 (если они этого еще не сделали) не должны изменять значение программы или изменяться независимо от того, компилируется она или нет. Поэтому использование изменяемого присваивания в лямбде обычно запрещено, и на то есть веская причина: мутация приводит к распараллеливанию, и одна из основных причин, по которой лямбды были включены в Java 8, заключалась в упрощении параллельного программирования.

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

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

Вы можете обновить свои переменные, сделав их статическими во внутренних лямбда-функциях.

Другие вопросы по тегам