365bet亚洲真人网址-365双试投注-365体育平台bet下载入口

为什么lambda表达式中的局部变量必须是有效或者最终有效的?

本文主要思路源自Why Do We Need Effectively Final? 一文并结合自己的思考所成。 有效最终(effectively final)从 Java8 开始,本地类可以访问最终或有效

为什么lambda表达式中的局部变量必须是有效或者最终有效的?

本文主要思路源自Why Do We Need Effectively Final? 一文并结合自己的思考所成。

有效最终(effectively final)从 Java8 开始,本地类可以访问最终或有效最终的封闭块的局部变量和参数。那么,什么是有效最终(effectively final)变量呢?

通常来讲,满足以下三个条件的变量我们即可以把这个变量称为有效最终( effectively final (§4.12.4)):

该参数没有声明 final 关键字(其实是废话,加了final的肯定就被称为 final 而非 effectively final 了)该参数永远不会出现在赋值表达式的左侧(除了初始化变量的时候,比如String s; s = "hello world!";)永远作为递增或递减的前缀和后缀运算的运算数出现(比如 i++中的i)简单来说,在初始化之后就不会再进行赋值的参数或者变量,我们就可以称之为“有效最终”了。(原文为 A variable or parameter whose value is never changed after it is initialized is effectively final. )

lambda表达式的限制在JLS中,明确指出,在lambda表达式内部使用的任何未在lambda表达式中声明的局部变量(local variable)、形式参数(formal parameter)以及异常参数(exception parameter)都必须是final或effectively final的,否则将会出现编译错误。另外,局部变量在lambda中使用时必须时初始化过的。

在下面的代码示例中,列出了几种常见的错误情况:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

public class TestLambda {

private String iv = "初始化成员变量";

public void test() {

List list = Collections.singletonList("1");

List newList = new ArrayList<>();

// 如果newList出现赋值,lambda表达式中就会报错

newList = Collections.singletonList("2");

String s1, s2;

int i = 1;

// 这样的初始化并不影响s1是有效最终的局部变量

s1 = "初始化的S1";

// 成员变量不受这个限制,可以随意赋值

iv = "修改成员变量";

list.forEach(t -> {

// newList必须是最终或者有效最终

newList.add(t);

// s1是有效最终,所以是可以使用的

newList.add(s1);

// 报错,lambda表示式中无法对s2进行初始化了

s2 = "lambda中初始化S2";

newList.add(s2);

// 报错,i++出现了赋值行为,破坏了i的有效最终

newList.add(i++);

iv = iv + "1";

// 成员变量iv是没问题的

iv = "lambda";

newList.add(iv);

System.out.println(t);

});

// 后续有任何赋值操作,lambda表达式中也会报错

newList = Collections.singletonList("2");

}

}

对于这种限制的原因,JLS中给出的原因是 The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems. 翻译过来就是,Lambda表达式中禁止捕获动态变化的局部变量是因为可能会导致并发问题。

下面我们具体来看下到底会有哪些问题。

1. 捕获局部变量我们先来看这么一段代码:

1

2

3

Supplier incrementer(int start) {

return () -> start++;

}

当然这里是无法编译的,报错如下图所示:

上面的代码中,lambda表达式的部分(start++)在Supplier.get()方法被调用之前都不会执行的,所以在lambda表达式捕获到局部变量start的值的时候会复制start的一份副本到lambda内部,而要求局部变量必须为最终或有效最终也是为了防止给人留下这样的印象:对lambda内部的start变量的修改可以影响到局部变量start。

再来看这段代码:

1

2

3

4

5

6

7

8

9

public void localVariableMultithreading() {

boolean run = true;

executor.execute(() -> {

while (run) {

// do operation

}

});

run = false;

}

上面的代码有很明显的“可见性”问题,我们知道每个线程都有自己的堆栈,那么我们如何确保while循环看到另一个堆栈中run变量的变化呢?答案可能是使用synchronized或volatile关键字。然而,由于lambda表达式中有效最终的限制,我们可以不必担心这样的复杂性。

2. 捕获成员变量或静态变量在本章一开始的代码示例中,我们可以看到成员变量是不受final或者有效final的限制的,这是因为成员变量是存储在堆(heap)内存的,编译器可以保证同一个线程内永远能够获取到成员变量的最新的值。

对于多线程的情况,我们可以使用volatile关键字来保证可见性。

上小节的代码我们用成员变量可以改写为:

1

2

3

4

5

private int start = 0;

Supplier incrementer() {

return () -> start++;

}

1

2

3

4

5

6

7

8

9

10

11

private volatile boolean run = true;

public void instanceVariableMultithreading() {

executor.execute(() -> {

while (run) {

// do operation

}

});

run = false;

}

总结Java8 限制lambda表达式中使用的局部变量必须是最终或者有效最终,是因为任何在该lambda类中使用的变量都会通过自动生成的构造函数复制一份新的变量,为了保证这种同步性并且防止引起不必要的并发问题,所以做此限制。

参考文档Difference between final and effectively finalWhy Do We Need Effectively Final?Why do local variables used in lambdas have to be final or effectively final?

← 上一篇: 俄罗斯妈妈一段跨越国界的情感故事
下一篇: 新加坡賭場 直擊,新加坡熱門景點CASINO,免費飲料咖啡喝到飽 →

相关推荐

饮水机怎么安装水桶?饮水机的安装流程

饮水机怎么安装水桶?饮水机的安装流程

虽然安装饮水机可能有些复杂,但通过了解正确的安装流程和有关知识,您知道如何安装饮水机和安装水桶,从而让安装更容易。 一、饮水机的

Java中如何定义全局变量

Java中如何定义全局变量

在Java中,要定义全局变量,您需要在类的主体中声明它,而不是在任何方法或代码块内。这样,该变量就可以在整个类的所有方法和构造函数中

中国男篮最高世界排名多少(中国男篮最高纪录)

中国男篮最高世界排名多少(中国男篮最高纪录)

本文目录一览: 1、中国篮球在世界上最好成绩是排在第几位? 2、fiba历年男篮排名? 3、中国男篮最好成绩是多少? 4、中国男篮历史最好成绩 5、

淘宝图片怎么做美工?用什么软件?

淘宝图片怎么做美工?用什么软件?

在淘宝电商运营中,商品图片的美工设计直接影响转化率。据统计,专业级视觉包装可使店铺点击率提升40%以上。本文将系统解析淘宝图片美工

𱍁的意思,𱍁的解释,𱍁的部首

𱍁的意思,𱍁的解释,𱍁的部首

〔𱍁〕字部首是龙部,总笔画是8画。 〔𱍁〕字是上下结构,可拆字为“龙、廾”。 〔𱍁〕字仓颉码是IPT,五笔是DXYA,郑码是GME。 〔𱍁〕字

克罗地亚世界杯26人名单出炉:40岁魔笛第五次征战,保留14名季军主力

克罗地亚世界杯26人名单出炉:40岁魔笛第五次征战,保留14名季军主力

北京时间6月2日,世界排名第14的克罗地亚国家男子足球队正式公布了参加2026年美洲世界杯的26人大名单,40岁的队长卢卡·莫德里奇继续领衔球