本文将会根据以下顺序进行叙述:
lambda 表达式中传入的局部变量,为什么需要是不可变的(final)?
函数式编程提倡的无状态。
无状态服务。
public class Demo {
public static void main(String[] args) {
int i = 0;
i = 2;
Thread thread = new Thread(() -> {
System.out.println(i);
});
}
}
这里声明了一个变量 i,初值为 0,然后对 i 的值做了修改,这让 i 成为一个真正意义上的变量。于是 lambda 表达式在编译时无法通过,报错 “lambda 表达式中的变量 i 应该是常量,或效果上与常量相当的”。
lambda 表达式中传入的局部变量,为什么需要是不可变的(final)?
lambda 表达式多作为回调函数来使用,是延迟计算的。当回调函数真正被触发时,外部传入回调函数的局部变量可能已经被改变,这违背了使用者的预期。jdk 在编译环节就否定了外部传入 lambda 表达式一个变量,只能是常量(即 final 修饰),或效果上与常量相当的(声明赋初值后就没有被修改过的变量),在编译环节禁止了这种风险。
public class Demo {
public static void main(String[] args) {
final int i = 0;
Thread thread = new Thread(() -> {
System.out.println(i);
});
int j = 1;
Thread thread1 = new Thread(() -> {
System.out.println(j);
});
}
}
像这样,我们声明一个常量 i,和一个效果上与常量相当的(声明赋初值后就没有被修改过的变量)j。i 和 j 都满足 lambda 表达式对外部传入的局部变量的要求,编译可以通过。
我们来看一下截图中的 lambda 表达式,编译后长什么样?
# 这是编译用的命令
javac Demo.java
# 这是反编译用的命令
javap -p Demo.class
public class lambda.concurrent.map.Demo {
public lambda.concurrent.map.Demo();
public static void main(java.lang.String[]);
private static void lambda$main$1(int);
private static void lambda$main$0();
可以看到,.java 文件的 thread1 中的 lambda 表达式,被编译成了当前类中一个叫 lambda$main$1(int);
的私有方法,原先 lambda 表达式中出现的外部局部变量,变成了此私有方法的入参。
根据实验得出结论:
还有一点疑惑,.java 文件的 thread 中的 lambda 表达式,引用了一个常量。这个常量,为什么没体现在根据 lambda 表达式生成的私有方法 lambda$main$0();
的参数列表中呢?
我们来看一下 IDEA 为我们反编译出的更直观的 .class 文件:
public class Demo {
public Demo() {
}
public static void main(String[] args) {
int i = false;
new Thread(() -> {
// 这行注释是我自己加的。请注意,常量 0,编译后直接替换掉了原先的变量符号
System.out.println(0);
});
int j = 1;
new Thread(() -> {
System.out.println(j);
});
}
}
我们可以看到,之前的常量 0,编译后直接替换掉了原先的变量符号。这是 javac 编译器的一种叫 “常量折叠” 的现象,可以在编译时完成对常量的计算工作,使 JVM 在运行字节码时更快速。
lambda 表达式是函数式编程的思想,而函数式编程是提倡无状态的,无状态是指什么呢?
复述一段引用:夏梓耀 - 知乎
一般所说的状态可视为
这样的三元组(引用,存储,值),reference 也可以叫 pointer,store 可看做是一个接受 reference 返回 value 的容器(具体实现可以是内存单元),value就是存储的值了; 状态变化是指两方面变化了:1. reference 改变,2. reference 所指向的 value 改变。
函数式编程提倡的无状态是指,“进去过的东西不因进去过而改变”,可以理解为不要在函数内修改由外部提供的变量的状态。
关于无状态编程的优势,可以看一下 stackOverflow 上的讨论: Advantages of stateless programming
总结一下自己阅读讨论后的理解:
比较简单的无状态实现方式,final 修饰变量,把变量标记为不可变。比如 String 类的设计,value 属性加了 fianl 修饰,这让 String 天然就是线程安全的。