Java 中的 Lambda 表达式不能访问局部变量?

问题现象

从 Java 8 开始新增的 Lambda 表达式,可以使代码变的更加简洁紧凑,使用中还会碰到一个问题:

Variable used in lambda expression should be final or effectively final

Lambda 表达式中使用的局部变量必须是 final 或者有效的 final 类型。

问题原因

从参考资料可以得知,对于如下的 Lambda:

// 使用注解 @FunctionalInterface 告诉编译器这是一个函数式接口
@FunctionalInterface
interface Print {
    void output(String str);
}

public class Main {
    private static void handle(String str, Print p) {
        p.output(str);
    }

    public static void main(String[] args) {
        handle("abc", str -> System.out.println(str));
    }
}

其等价于下面的代码:

interface Print {
    void output(String str);
}

public class Main {
    private static void handle(String str, Print p) {
        p.output(str);
    }

    // 编译后生成的私有静态方法,方法内容就是 Lambda 里的内容
    private static void lambda$main$0(String str) {
        System.out.println(str);
    }

    // 运行时生成的内部类,实现函数式接口,实现方法中调用私有静态方法
    final class Main$$Lambda$1 implements Print {
        @Override
        public void output(String str) {
            lambda$main$0(str);
        }
    }

    public static void main(String[] args) {
        Print print = new Main().new Main$$Lambda$1();
        handle("abc", print);
    }
}

即 Lambda 表达式是通过运行时实现函数式接口的匿名内部类来调用编译后生成的私有静态方法来实现的。而 Java 8 之前,匿名类中如果要访问局部变量的话,那个局部变量必须显式的声明为 final(为了避免数据不一致,局部变量值变化无法通知匿名类)。Java 8 之后,在匿名类或 Lambda 表达式中访问的局部变量,如果不是 final 类型的话,编译器自动加上 final 修饰符,即 Java 8 新特性:effectively final(有效的 final)。

解决方案

使用全局变量

将局部变量修改为全局变量即可在 Lambda 表达式中使用,不建议使用。

将局部变量放入数组中

将局部变量放入长度为 1 的数组中:

// 总装机容量
BigDecimal[] totalPowerCapacity = {BigDecimal.ZERO};
thingFilterMap.forEach((stationId, thingInfo) -> {
    totalPowerCapacity[0] = totalPowerCapacity[0].add(thingInfo.getPowerCapacity);
}

参考资料

Variable used in lambda expression should be final or effectively final
【JAVA】Lambda 执行原理

posted @ 2022-11-02 20:50  ageovb  阅读(349)  评论(0编辑  收藏  举报