java 在封闭作用域中定义的局部变量log必须是final或有效的final

cs7cruho  于 2023-09-29  发布在  Java
关注(0)|答案(6)|浏览(147)

我是lambda和Java8的新手。我正面临以下错误。
在封闭作用域中定义的局部变量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;
}
emeijp43

emeijp431#

消息准确地说明了问题所在:你的变量 log 必须是final(即:携带关键字final)或实际上是final(即:你只在lambda之外给它赋值 * 一次 *)。否则,您不能在lambda语句中使用该变量。
当然,这与您使用 log 的方式相冲突。关键是:你不能从lambda内部写入外部的东西。所以你必须退一步,为你打算做的任何事情寻找其他方法。
在这个意义上:相信编译器。
除此之外,还有一个核心点需要理解:你可以使用一个你可以写的局部变量。局部变量在运行时被“复制”到lambda的上下文中,为了实现确定性行为,它们只能被读取,并且应该是常量
如果你的用例是 * 写 * 到某个对象,那么它应该是你的封闭类的一个字段。
长话短说

*本地lambda内部使用(读取)的变量必须像常量一样

  • 你不能到局部变量!
  • 或者反过来如果你需要写一些东西,你必须使用你周围类的一个字段(或者提供一个回调方法)
iyr7buue

iyr7buue2#

这个限制的原因和Java语言特性的原因一样,从(匿名)内部类访问的 * 局部变量 * 必须是(有效地)final
rgettman的This answer详细介绍了它。rgettman清楚地详细解释了限制,我链接到那个答案,因为lambda表达式的行为应该与匿名内部类的行为相同。但是,请注意,对于类或示例变量不存在这种限制。主要原因有点复杂,我无法解释它比什么罗迪绿色在这里做的更好。在此复制,以便它位于一个位置:
规则是匿名内部类只能访问封闭方法的final局部变量。为什么呢?因为内部类的方法可能会在以后被调用,在产生它的方法终止之后很久,例如. AWT(Advanced Windowing Toolkit)本地变量早就消失了。然后,匿名类必须处理它所需要的那些被编译器秘密地存储在匿名内部类对象中的快速冻结副本。你可能会问,为什么局部变量必须是final的?难道编译器就不能像对待非final参数那样,对非final局部变量进行复制吗?如果它这样做了,你将有两个变量的副本。每一个都可以独立地改变,就像调用者和被调用者的参数副本一样,但是您可以使用相同的语法来访问任何一个副本。这会让人很困惑。所以孙坚持当地是最后的。这使得它实际上有两个副本变得无关紧要。
匿名类访问调用者的最终局部变量的能力实际上只是语法上的糖衣,用于自动传递一些局部变量作为额外的构造函数参数。我闻起来都是稀释过的杂七杂八水。

hlswsv35

hlswsv353#

在某些使用情况下,可以有一个变通方案。下面的代码抱怨startTime变量不是有效的final:

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函数中改变一个局部变量,那就不起作用了。

jtoj6r0c

jtoj6r0c4#

记住,方法内部类不能修改其周围方法的任何值。foreach中的第二个lambda表达式试图访问其周围的方法变量(log)。
为了解决这个问题,你可以避免在foreach中使用lambda,所以一个简单的for each并替换log中的所有值。

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

5m1hhzi45#

如果不想创建自己的对象 Package 器,可以使用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();
slhcrj9b

slhcrj9b6#

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

public abstract class ValueContext<T> {

    public T value;

    public abstract void run();
}

然后像这样使用它(String值的示例):

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;

相关问题