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

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

但是这不是一个好的习惯。 以上代码也可以由。

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

其他回答

从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();
}   

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和在method-parameter上使用final关键字的部分代码。

在您的示例中,您可以使用简单的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];
}

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

JLS在§15.27.2中提到了原因:

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

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


这也适用于匿名内部类