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

use*_*470 107 java lambda

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;
}
Run Code Online (Sandbox Code Playgroud)

Vin*_*igh 67

虽然其他答案证明了这一要求,但它们并未解释为何存在这一要求.

JLS在§15.27.2中提到了为什么:

对有效最终变量的限制禁止访问动态变化的局部变量,其捕获可能会引入并发问题.

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

  • 好的答案+1,我很惊讶_reason_对于有效决赛的覆盖范围似乎很少.值得注意的是:如果一个局部变量只能在lambda的主体之前明确分配_also_,那么它只能被lambda捕获.这两个要求似乎都确保访问局部变量是线程安全的. (9认同)
  • @DavidRefaeli类成员受内存模型覆盖/影响,如果遵循该成员,则共享时将产生可预测的结果。如[§17.4.1](https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.4.1)所述,本地变量不是。 (3认同)
  • 这是以这种方式阻碍 lamda 的胡扯理由。如果您不了解多线程,您可能会有所了解,但您也可能会学到一些东西。 (3认同)
  • 任何想法为什么这只限于局部变量,而不是类成员?我发现自己经常通过将我的变量声明为类成员来规避问题。 (2认同)

Fra*_*lis 57

final变量意味着它可以被实例化一次.在Java中,您不能在lambda和匿名内部类中使用非final变量.

您可以使用旧的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;
}
Run Code Online (Sandbox Code Playgroud)

即使我没有理解这些代码的某些部分:

  • v.getTimeZoneId();不用使用它的返回值就调用a
  • calTz = TimeZone.getTimeZone(v.getTimeZoneId().getValue());如果您没有修改最初传递的作业,则不要calTz在此方法中使用它
  • 你总是回来null,为什么不设置void为返回类型?

希望这些技巧也可以帮助您改进.


小智 46

从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();
}   
Run Code Online (Sandbox Code Playgroud)

  • 这应该是第一个答案.AtomicReference(或类似的Atomic___类)可以在每种可能的情况下安全地解决此限制. (6认同)
  • 这段代码错过了一个初始`reference.set(calTz);`或者必须使用`new AtomicReference <>(calTz)`创建引用,否则作为参数提供的非null TimeZone将丢失. (4认同)
  • @GlenPeterson,这也是一个糟糕的决定,不仅这样会慢很多,而且您还忽略了文档规定的副作用属性。 (3认同)

Din*_*ora 37

Java 8有一个名为"Effectively final"变量的新概念.这意味着一个非最终的局部变量,其值在初始化后永远不会改变,称为"有效最终".

引入这个概念是因为在Java 8之前,我们不能在一个非最终的局部变量中使用final.如果您想访问本地变量final,则必须使其成为最终变量.

什么final时候推出,这个限制得到了缓解.因此,final如果局部变量一旦被初始化就不会被改变,因此需要制作局部变量本身只是一个匿名类.

Java 8意识到final每次开发人员使用final和引入这个概念时都会声明局部变量,并且无需创建局部变量final.因此,如果您看到规则final仍未更改,则只需final每次使用时都不必编写关键字final.

我在这里找到了一个很好的解释


Ale*_*lov 6

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

Java 8语言规范,§15.27.2:

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

基本上你不能calTz从lambda(或本地/匿名类)中修改局部变量(在这种情况下).要在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];
}
Run Code Online (Sandbox Code Playgroud)