lambda表达式中使用的变量应该是final或有效final

lambda表达式中使用的变量应该是final或有效final

当我尝试使用calTz时,它显示此错误。

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if (calTz == null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}
332638 次浏览

从lambda中,你不能得到指向非final对象的引用。您需要从lamda外部声明一个最终包装器来保存您的变量。

我已经添加了最终的“引用”对象作为这个包装器。

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
final AtomicReference<TimeZone> reference = new AtomicReference<>();


try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component->{
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(reference.get()==null) {
reference.set(TimeZone.getTimeZone(v.getTimeZoneId().getValue()));
}
});
} catch (Exception e) {
//log.warn("Unable to determine ical timezone", e);
}
return reference.get();
}

在你的例子中,你可以用一个简单的for循环将forEach替换为lamdba,并自由修改任何变量。或者重构代码,这样就不需要修改任何变量。但是,为了完整起见,我将解释这个错误的含义以及如何解决它。

Java 8语言规范,§15.27.2:

任何在lambda表达式中使用但未声明的局部变量、形式形参或异常形参必须声明为final或有效的final (§4.12.4),否则在尝试使用时发生编译时错误。

基本上,你不能在lambda(或本地/匿名类)内修改局部变量(在本例中为calTz)。要在Java中实现这一点,必须使用可变对象并从lambda中修改它(通过final变量)。可变对象的一个例子是一个元素数组:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
TimeZone[] result = { null };
try {
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
...
result[0] = ...;
...
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return result[0];
}
final变量意味着它只能被实例化一次。 在Java中,你不能在lambda和匿名内部类中重新分配非最终局部变量

你可以用旧的for-each循环重构你的代码:

private TimeZone extractCalendarTimeZoneComponent(Calendar cal,TimeZone calTz) {
try {
for(Component component : cal.getComponents().getComponents("VTIMEZONE")) {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
if(calTz==null) {
calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
}
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}

即使我不明白这段代码的某些部分:

  • 调用v.getTimeZoneId();而不使用它的返回值
  • 对于赋值calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());,你不会修改最初传递的calTz,也不会在这个方法中使用它
  • 你总是返回null,为什么不设置void为返回类型呢?

希望这些建议也能帮助你提高。

Java 8有一个叫做“有效最终”变量的新概念。这意味着一个在初始化后值从未改变的非最终局部变量被称为“有效最终”。

引入这个概念是因为在Java 8之前,我们不能在匿名类中使用非最终局部变量。如果你想访问匿名类中的局部变量,你必须使它为final。

引入lambda后,这种限制得到了缓解。因此,如果局部变量在初始化为lambda后没有改变,那么它就需要成为final,因为lambda本身只是一个匿名类。

Java 8意识到每次开发人员使用lambda时声明局部变量为final的痛苦,引入了这个概念,并使局部变量不需要为final。因此,如果你看到匿名类的规则没有改变,你就不必每次使用lambdas时都写final关键字。

我找到了一个很好的解释

如果没有必要修改变量比一般的解决方法这类问题将 提取代码中使用lambda的部分,并在method-parameter上使用final关键字

虽然其他答案证明了需求,但他们不能解释为什么需求的存在。

JLS在§15.27.2中提到了原因:

对有效最终变量的限制禁止访问动态更改的局部变量,捕获这些局部变量可能会引入并发性问题。

为了降低错误的风险,他们决定确保捕获的变量永远不会发生突变。


这也适用于匿名内部类

lambda表达式中使用的变量应该是final或有效的final,但可以将值赋给最终的单元素数组。

private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) {
try {
TimeZone calTzLocal[] = new TimeZone[1];
calTzLocal[0] = calTz;
cal.getComponents().get("VTIMEZONE").forEach(component -> {
TimeZone v = component;
v.getTimeZoneId();
if (calTzLocal[0] == null) {
calTzLocal[0] = TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
} catch (Exception e) {
log.warn("Unable to determine ical timezone", e);
}
return null;
}

对>lambda表达式中使用的变量应该是final或有效的final JAVA

以一种不优雅的方式解决这个问题,2个问题: 副作用和线程问题

final AtomicInteger e = new AtomicInteger(0);
new Thread(() -> {
e.addAndGet(1);
});

更准确地说,我同意是一种相同的,但使用Lambda函数背后的想法是避免副作用,当我们访问Lambda函数中的最终引用来填充值以从外部获得结果时,我们打破了这个概念。

在最老的帖子中,你可能想这样重写

cal.getComponents () .getComponents(“VTIMEZONE") .streams () . map (v→v.getTimeZoneId () .getValue ()) .collect (Collectors.toList ());

对于线程方面,我们也有同样的副作用问题,此外,你永远不知道什么时候访问原子变量来收集结果,你可以放一个CountDownLatch…最好使用CompletableFuture来处理结果和同步方面

你不能在lambda表达式中使用来自lambda作用域之外的新引用重新分配变量。但是您当然可以修改对象的现有状态。因此,取而代之的是将'calTz'重新分配给新引用。你可以调用它的setter方法来改变它的内部状态。所以这将工作(如果你的VtimeZone是可变的):

    calTz=new TimeZone();
cal.getComponents().getComponents("VTIMEZONE").forEach(component -> {
VTimeZone v = (VTimeZone) component;
v.getTimeZoneId();
                    

calTz.setTimeZoneId("some value");
                    

})
< p >。但这不是一个好的做法。 以上代码也可以用。

代替
        if(calTz == null){
calTz=new TimeZone();
cal.getComponents().getComponents("VTIMEZONE").get(0).setTimeZoneId("some value");}