lambda 中的 Java 8 lambda 无法从外部 lambda 修改变量

Posted

技术标签:

【中文标题】lambda 中的 Java 8 lambda 无法从外部 lambda 修改变量【英文标题】:Java 8 lambda within a lambda can't modify variable from outer lambda 【发布时间】:2014-06-20 12:31:01 【问题描述】:

假设我有一个List<String> 和一个List<Transfomer>。我想将每个转换器应用于列表中的每个字符串。

使用 Java 8 lambda,我可以做到这一点:

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

我刚开始使用 lambda,所以也许我的语法不正确。

【问题讨论】:

包含编译器错误的文本可能会有所帮助。 错误:(49, 60) java: 从 lambda 表达式引用的局部变量必须是最终的或有效的最终 这个问题很好;但是,我建议将其移至 Stack Overflow。 有什么办法可以移动它吗?还是需要比我更多的权限? 【参考方案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);

出现编译器错误的原因是,正如错误所说,lambda 表达式中使用的外部变量必须实际上是最终的;也就是说,声明它们final(如果它们还没有)不能改变程序的含义,或者改变它是否编译。因此,通常禁止在 lambda 中使用可变赋值,这是有充分理由的:突变会破坏并行化,而 Java 8 中包含 lambda 的主要原因之一是允许更轻松的并行编程。

一般来说,每当您想以某种方式“总结”结果时,reduce(在它的三个重载中的任何一个中)都是您的首选方法。在使用Streams 时,学习如何有效地使用mapfilterreduceflatMap 非常重要。

【讨论】:

谢谢,这对于我使用 lambdas 的有限经验来说是相当多的。语法有点混乱,但我会一直讲到我理解为止:) +1,但我会发现如果 cmets 较短或在单独的行上以便没有水平滚动,则更容易阅读代码。 @DavidConrad 现在看起来怎么样? 不横向滚动肯定更好。这么多空行是否更好,我留给你判断。 如果您使用预定义的 UnaryOperator 而不是 Transformer,您可以使用 UnaryOperator.identity() 而不是 x->x。它总是返回相同的对象,而 x->x 不必要地创建了一个新的 lambda 对象。【参考方案2】:

Lambda(就像局部类一样)永远不能分配给捕获的局部变量,无论是来自外部 lambda,还是来自封闭方法。捕获的局部变量必须是有效的最终变量。

【讨论】:

以上是关于lambda 中的 Java 8 lambda 无法从外部 lambda 修改变量的主要内容,如果未能解决你的问题,请参考以下文章

lambda 中的 Java 8 lambda 无法从外部 lambda 修改变量

Java 8中的lambda表达式

java8的lambda表达式

Java 8 中的 Lambda 表达式

java 8 lambda表达式中的异常处理

Java 8 lambda表达式中的异常处理