Java lambda表达式中使用的变量应为final或final

Java lambda表达式中使用的变量应为final或final,java,lambda,Java,Lambda,lambda表达式中使用的变量应为final或final 当我尝试使用calTz时,它显示了此错误 private TimeZone extractCalendarTimeZoneComponent(Calendar cal, TimeZone calTz) { try { cal.getComponents().getComponents("VTIMEZONE").forEach(component -> { VTimeZone v = (

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;
}

从lambda中,您无法获得任何非最终的引用。您需要从lamda外部声明一个最终包装器来保存变量

我已经添加了最后一个“reference”对象作为这个包装器

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();
}   
private TimeZone extractCalendarTimeZoneComponent(日历校准,时区校准){
最终原子引用=新原子引用();
试一试{
cal.getComponents().getComponents(“VTIMEZONE”).forEach(组件->{
VTimeZone v=(VTimeZone)组件;
v、 getTimeZoneId();
if(reference.get()==null){
set(TimeZone.getTimeZone(v.getTimeZoneId().getValue());
}
});
}捕获(例外e){
//log.warn(“无法确定ical时区”,e);
}
返回引用.get();
}   

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

Java 8语言规范:

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

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

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
    设置为返回类型
希望这些技巧也能帮助您改进。

Java8有一个新概念,称为“有效最终”变量。这意味着非最终局部变量的值在初始化后不会改变,称为“有效最终”

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

引入lambda后,这一限制得到了放松。因此,如果局部变量在初始化为lambda后没有更改,则需要将其设置为final,这本身就是一个匿名类

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

我发现了一个很好的解释

如果不需要修改变量,那么这类问题的一般解决方法是
提取使用lambda并在方法参数上使用final关键字的代码部分。

尽管其他答案证明了需求,但它们没有解释需求存在的原因

JLS提到了以下原因:

对有效的最终变量的限制禁止访问动态变化的局部变量,这些变量的捕获可能会导致并发问题


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

lambda表达式中使用的变量应该是final或有效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修改
calTz
,我认为这是Java8没有及时完成的事情之一。但Java8是2014年发布的。Scala和Kotlin多年来一直允许这样做,所以这显然是可能的。Java是否曾计划消除这种奇怪的限制?是@M.S.Dousti评论的更新链接。我认为你可以使用可完成的期货作为解决办法。我观察到的一件重要事情是,你可以使用静态变量而不是普通变量(我想这是最终的结果)我正在考虑相同或类似的方法-但我希望看到一些专家对这个答案的建议/反馈?这个代码缺少一个初始的
reference.set(calTz)
或必须使用
新建原子引用(calTz)
创建引用,否则作为参数提供的非空时区将丢失。这应该是第一个答案。原子引用(或类似的原子类)可以在任何可能的情况下安全地绕过此限制。同意,这应该是公认的答案。其他答案提供了关于如何回到非函数式编程模型的有用信息,以及为什么要这样做,但实际上并没有告诉您如何解决这个问题@GlenPeterson,这也是一个糟糕的决定,不仅如此,这样做的速度要慢得多,而且您还忽略了文档要求的副作用属性。回答好+1,我很惊讶地发现,有效最终结果的原因几乎没有得到报道。注意:局部变量只能在b中捕获