我是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;
}
答案 0 :(得分:19)
该消息确切地说明了问题所在:您的变量 log 必须是最终的(即:携带关键字final)或者实际上是最终的(即:您只为其分配值< em>一次在lambda之外)。否则,您无法在lambda语句中使用该变量。
但当然,这与您使用 log 相冲突。关键是:你不能在lambda中写一些外部的东西......所以你必须退后一步,为你想做的事情寻找其他方法。
从这个意义上讲:只要相信编译器。
除此之外,还有一个核心要点:您可以不使用您可以写入的本地变量。局部变量被复制&#34;在运行时进入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();