在封闭范围内定义的局部变量日志必须是最终的或有效的最终

时间:2016-07-15 18:00:03

标签: java apache-spark lambda java-8

我是lambda和Java8的新手。我正面临以下错误。

  

在封闭范围内定义的局部变量日志必须是final或   有效的最终

public JavaRDD<String> modify(JavaRDD<String> filteredRdd) {

    filteredRdd.map(log -> {

        placeHolder.forEach(text -> {

            //error comes here
            log = log.replace(text, ",");

        });

        return log;

    });

    return null;
}

6 个答案:

答案 0 :(得分:19)

该消息确切地说明了问题所在:您的变量 log 必须是最终的(即:携带关键字final)或者实际上是最终的(即:您只为其分配值< em>一次在lambda之外)。否则,您无法在lambda语句中使用该变量。

但当然,这与您使用 log 相冲突。关键是:你不能在lambda中写一些外部的东西......所以你必须退后一步,为你想做的事情寻找其他方法。

从这个意义上讲:只要相信编译器。

除此之外,还有一个核心要点:您可以使用您可以写入的本地变量。局部变量被复制&#34;在运行时进入lambda的上下文,并且为了实现确定性行为,它们只能被读取,并且应该是常量

如果您的用例是到某个对象,那么它应该是您的封闭类的字段,例如!

所以,长话短说:

    在lambda中使用(读取)的
  • 本地变量必须像常量
  • 你不能到局部变量!

答案 1 :(得分:11)

此限制的原因与从(匿名)内部类中访问的局部变量必须(有效)最终的Java语言功能相同的原因相同。

rgettman的

This answer深入了解它的细节。 rgettman以清晰的细节解释了这些局限性,并链接到该答案,因为lambda表达式的行为应该与匿名内部类的行为相同。请注意,对于类或实例变量,不存在此类限制。造成这种情况的主要原因有点复杂,我无法解释它比Roedy Green所做的更好here。只在这里复制才能在一个地方:

  

规则是匿名内部类只能访问最终本地   封闭方法的变量。为什么?因为内在阶级的   在产生它的方法之后很久就可以调用方法   已终止,例如通过AWT(高级窗口工具包)事件。该   局部变量早已不复存在。然后匿名类必须使用   快速冻结的副本,只需要隐藏的东西   由编译器在匿名内部类对象中。你可能会问,   为什么局部变量必须是最终的?无法编译   同样也要采用非最终局部变量的副本,就像它的方式一样   对于非最终参数?如果它这样做,你将有两个   变量的副本。每个都可以独立改变,就像   调用者和被调用者的参数副本,但是你会使用   访问任一副本的语法相同。这会令人困惑。所以太阳   坚持认为当地是最终的。这使得无关紧要   实际上是它的两个副本。

     

匿名类访问调用者的最终本地的能力   变量实际上只是用于自动传入的语法糖   一些局部变量作为额外的构造函数参数。整个东西   我觉得有稀释的eau de kludge。

答案 2 :(得分:2)

记住方法内部类不能修改其周围方法中的任何值。 forecach中的第二个lambda表达式试图访问其周围的方法变量(log)。

要解决这个问题,你可以避免为每个使用lambda,因此每个都很简单,并重新记录日志中的所有值。

        filteredRdd.map(log -> {
        for (String text:placeHolder){
            log = log.replace(text,",");
        }
        return log;
    });

答案 3 :(得分:2)

在某些情况下,可能会变通。以下代码抱怨startTime变量实际上不是最终变量:

List<Report> reportsBeforeTime = reports.stream()
                                        .filter(r->r.getTime().isAfter(startTime))
                                        .collect(Collectors.toList());

因此,只需将值复制到最终变量,然后再将其传递给lambda:

final LocalTime finalStartTime = startTime;
    List<Report> reportsBeforeTime = reports.stream()
                                            .filter(r->r.getTime().isAfter(finalStartTime))
                                            .collect(Collectors.toList());

但是,如果您需要在lambda函数中更改局部变量,则此操作将无效。

答案 4 :(得分:0)

一种解决方案是将代码封装在一个封闭的(内部类)中。您可以定义以下内容:

    public abstract class ValueContext<T> {

    public T value;

    public abstract void run();
}

然后像这样使用它(一个字符串值的示例):

final ValueContext<String> context = new ValueContext<String>(myString) {

@Override
public void run() { 

// Your code here; lambda or other enclosing classes that want to work on myString,
// but use 'value' instead of 'myString'

        value = doSomethingWithMyString(value);

}};

context.run();

myString = context.value;

答案 5 :(得分:0)

如果不想创建自己的对象包装器,可以使用AtomicReference,例如:

AtomicReference<String> data = new AtomicReference<>();

Test.lamdaTest(()-> {
    //data = ans.get(); <--- can't do this, so we do as below
    data.set("to change local variable"); 
});

return data.get();