Java 在封闭范围内定义的局部变量 log 必须是 final 或有效 final

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38402493/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 20:16:58  来源:igfitidea点击:

Local variable log defined in an enclosing scope must be final or effectively final

javaapache-sparklambdajava-8

提问by Balaji Reddy

I'm new to lambda and Java8. I'm facing following error.

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

Local variable log defined in an enclosing scope must be final or effectively final

在封闭范围内定义的局部变量 log 必须是 final 或有效 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;
}

采纳答案by GhostCat

The message says exactly what the problem is: your variable logmust be final (that is: carry the keyword final) or be effectively final (that is: you only assign a value to it onceoutside of the lambda). Otherwise, you can't use that variable within your lambda statement.

该消息准确地说明了问题所在:您的变量log必须是最终的(即:携带关键字 final)或有效地最终(即:您只在 lambda 之外为其赋值一次)。否则,您不能在 lambda 语句中使用该变量。

But of course, that conflicts with your usage of log. The point is: you can't write to something external from within the lambda ... so you have to step back and look for other ways for whatever you intend to do.

但当然,这与您对log的使用相冲突。关键是:你不能从 lambda 内部写入外部的东西......所以你必须退后一步,为你打算做的任何事情寻找其他方法。

In that sense: just believe the compiler.

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

Beyond that, there is one corepoint to understand: you can notuse a local variable that you can write to. Local variables are "copied" into the context of the lambda at runtime, and in order to achieve deterministic behavior, they can only be read, and should be constants.

除此之外,还有一个核心要点需要理解:您不能使用可以写入的局部变量。局部变量在运行时被“复制”到 lambda 的上下文中,为了实现确定性行为,它们只能被读取,并且应该是常量

If your use case is to writeto some object, then it should be a field of your enclosing class for example!

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

So, long story short:

所以,长话短说:

  • localvariables used (read) inside a lambda must act like a constant
  • you can not writeto local variables!
  • or the other way round: if you need something to write to, you have to use a field of your surrounding class for example (or provide a call back method)
  • 在 lambda 中使用(读取)的局部变量必须像一个常量一样
  • 你不能局部变量!
  • 或者反过来:如果你需要写一些东西,你必须使用你周围类的一个字段(或提供一个回调方法)

回答by Kedar Mhaswade

The reason for this limitation is the same as the reason for the Java language feature that local variablesaccessed from within (anonymous) inner classes must be (effectively) final.

此限制的原因与 Java 语言特性的原因相同,即从(匿名)内部类访问的局部变量必须(有效)为 final

This answerby rgettman gets into the details of it. rgettman explains the limitations in clear detail and I link to that answer because the behavior of lambda expressions should be same as that of anonymous inner classes. Note that such limitation does not exist for class or instance variables, however. The main reason for this is slightly complicated and I couldn't explain it better than what Roedy Green does it here. Copying here only so it is at one place:

rgettman 的这个答案详细介绍了它。rgettman 清楚详细地解释了这些限制,我链接到该答案,因为 lambda 表达式的行为应该与匿名内部类的行为相同。但是请注意,类或实例变量不存在此类限制。这样做的主要原因有点复杂,我无法比 Roedy Green 在这里做的更好地解释它。只在这里复制,所以它在一个地方:

The rule is anonymous inner classes may only access final local variables of the enclosing method. Why? Because the inner class's methods may be invoked later, long after the method that spawned it has terminated, e.g. by an AWT (Advanced Windowing Toolkit) event. The local variables are long gone. The anonymous class then must work with flash frozen copies of just the ones it needs squirreled away covertly by the compiler in the anonymous inner class object. You might ask, why do the local variables have to be final? Could not the compiler just as well take a copy of non-final local variables, much the way it does for a non-final parameters? If it did so, you would have two copies of the variable. Each could change independently, much like caller and callee's copy of a parameter, however you would use the same syntax to access either copy. This would be confusing. So Sun insisted the local be final. This makes irrelevant that there are actually two copies of it.

The ability for an anonymous class to access the caller's final local variables is really just syntactic sugar for automatically passing in some local variables as extra constructor parameters. The whole thing smells to me of diluted eau de kludge.

规则是匿名内部类只能访问封闭方法的最终局部变量。为什么?因为内部类的方法可能会在稍后调用,在产生它的方法已经终止很久之后,例如通过 AWT(高级窗口工具包)事件。局部变量早已不复存在。然后,匿名类必须使用它需要的那些被编译器在匿名内部类对象中秘密隐藏的副本的闪存冻结副本。你可能会问,为什么局部变量必须是 final 的?编译器难道不能像处理非最终参数那样复制非最终局部变量吗?如果这样做,您将拥有该变量的两个副本。每个都可以独立更改,就像调用者和被调用者的参数副本一样,但是,您可以使用相同的语法来访问任一副本。这会令人困惑。所以孙坚坚持当地是最终的。这使得它实际上有两个副本无关紧要。

匿名类访问调用者的最终局部变量的能力实际上只是一种语法糖,用于自动将一些局部变量作为额外的构造函数参数传入。整件事让我闻到稀释的淡香水。

回答by Mr.Q

Remember method inner classes can`t modify any value from their surrounding method. Your second lambda expression in forecach is trying to access its surrounding method variable (log).

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

To solve this you can avoid using lambda in for each and so a simple for each and re-palace all the values in log.

为了解决这个问题,您可以避免在每个中使用 lambda,因此对每个都使用简单并重新设置日志中的所有值。

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

回答by Deb

In some use cases there can be a work around. The following code complains about the startTimevariable not being effectively final:

在某些用例中,可以有一个变通方法。以下代码抱怨startTime变量不是有效的最终变量:

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

So, just copy the value to a final variable before passing it to lambda:

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

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

However, If you need to change a local variable inside a lambda function, that won't work.

但是,如果您需要更改 lambda 函数内的局部变量,那将不起作用。

回答by Kjeld

One solution is to encapsulate the code in an enclosing (inner class). You can define this:

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

    public abstract class ValueContext<T> {

    public T value;

    public abstract void run();
}

And then use it like this (example of a String value):

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

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;