Java 1.8 Lambda 如何访问局部变量,成员变量?

Java 1.8 Lambda 如何访问局部变量,成员变量?

Java 1.8 Lambda 如何访问局部变量,成员变量?

1. 访问局部变量

在 Lambda 表达式中访问局部变量时,该局部变量实际上必须是 final 或者是 “有效 final” 的。“有效 final” 指的是变量在初始化后没有再被重新赋值。

所谓有效 final,就是在一个方法里面,只被定义赋值了一次,相当于是final。

示例代码

import java.util.function.Consumer;

public class LambdaLocalVariable {

public static void main(String[] args) {

int num = 10; // 有效 final 变量

Consumer consumer = (n) -> System.out.println(n + num);

// num = 20; // 如果取消注释,编译会报错,因为改变了 num 的值,它不再是有效 final

consumer.accept(5);

}

}

原因分析

Lambda 表达式会捕获局部变量的值而不是变量本身。这是因为局部变量存储在栈上,当 Lambda 表达式所在的方法执行完毕后,局部变量可能已经被销毁。为了保证 Lambda 表达式可以正确访问局部变量的值,Java 要求局部变量必须是 final 或者 “有效 final” 的。

Lambda 表达式捕获局部变量的原理

Lambda 表达式会捕获局部变量的值,也就是把局部变量的值复制一份到 Lambda 表达式内部。这样做的原因是,局部变量存储在栈上,其生命周期受限于方法的执行,当方法执行结束后,局部变量可能会被销毁。通过捕获局部变量的值,能保证 Lambda 表达式在后续执行时可以访问到正确的值。

public class LambdaReferenceReassignment {

public static void main(String[] args) {

List list = new ArrayList<>();

list.add("Initial");

// 定义一个 Lambda 表达式,捕获局部变量 list

List finalList = list;

Consumer consumer = (str) -> {

try {

//故意停一会

Thread.sleep(1000);

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

finalList.add(str);

System.out.println(finalList);

};

// 重新赋值引用

list = new ArrayList<>();

System.out.println("此刻外面的list=" + list);

System.out.println("此刻外面的finalList=" + finalList);

// 调用 Lambda 表达式

consumer.accept("New ");

}

}

运行结果:

此刻外面的list=[]

此刻外面的finalList=[Initial]

[Initial, New ]

finalList相当于就是被lamda代码给锁住了,保留了当前方法内的快照。

误区:lamda访问的局部变量,一定是不变的。

错了,不是不可变,而是不可重新赋值,确保lamda访问的是同一个变量,你当然可以去操作list,那么lamda中访问的就是最新的list。

2. 访问成员变量

Lambda 表达式访问成员变量时,不需要将成员变量声明为 final,因为成员变量的生命周期与对象的生命周期相同,只要对象存在,成员变量就可以被访问。

示例代码

import java.util.function.Consumer;

public class LambdaInstanceVariable {

private int instanceVar = 10;

public void testLambda() {

Consumer consumer = (n) -> System.out.println(n + instanceVar);

instanceVar = 20; // 可以修改成员变量的值

consumer.accept(5);

}

public static void main(String[] args) {

LambdaInstanceVariable obj = new LambdaInstanceVariable();

obj.testLambda();

}

}

原因分析

成员变量存储在堆上,与对象的生命周期相关。Lambda 表达式可以通过对象引用访问成员变量,因此不需要将成员变量声明为 final。

3. 访问静态成员变量

Lambda 表达式访问静态成员变量时,也不需要将静态成员变量声明为 final,因为静态成员变量属于类,在类加载时就已经分配了内存,只要类存在,静态成员变量就可以被访问。

示例代码

import java.util.function.Consumer;

public class LambdaStaticVariable {

private static int staticVar = 10;

public static void testLambda() {

Consumer consumer = (n) -> System.out.println(n + staticVar);

staticVar = 20; // 可以修改静态成员变量的值

consumer.accept(5);

}

public static void main(String[] args) {

testLambda();

}

}

原因分析

静态成员变量存储在方法区,与类的生命周期相关。Lambda 表达式可以通过类名直接访问静态成员变量,因此不需要将静态成员变量声明为 final。

看一个例子:

public static void main(String[] args) {

List list = new ArrayList<>();

list.add("Initial");

// 定义一个 Lambda 表达式,捕获局部变量 list

List finalList = list;

Runnable runnable = () -> {

try {

//故意停一会

Thread.sleep(1000);

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

finalList.add("new");

System.out.println(finalList);

};

new Thread(runnable).start(); //开启线程,但是会过一会再读取到finalList

//我在这里改了,没想到吧,这会线程还没读到finalList呢

finalList.add("23456");

}

输出:[Initial, 23456, new]

方法都结束了,线程还在跑,按理说方法结束,finalList在栈上,已经被回收了,但是因为有lamda,就好像js的闭包一样被锁住了,从此,finalList跟着lamda的生命周期混了,延续了下去,很神奇吧。这是一个很有意思的话题了,后期我们专门写一篇文章来聊聊这个事情。

总结

只有在 Lambda 表达式访问局部变量时,才需要局部变量是 final 或者 “有效 final” 的,以确保 Lambda 表达式能够正确访问局部变量的值。而访问成员变量和静态成员变量时,由于它们的生命周期与对象或类相关,不需要声明为 final。

Java 的 Lambda 表达式捕获局部变量的机制和 JavaScript 的闭包有相似之处。它们都允许在一个函数(或方法)内部访问外部作用域的变量,并且即使外部作用域已经执行完毕,这些变量的值依然可以被保留和使用。

相关推荐