直接的答案
JLS给我们了一些提示,它说对有效的最终变量的限制对动态变化的本地变量的访问,本地变量可能会引入并发问题。但是,这是什么英文呢?
简单的说:java为了防止修改数据不同步而定的,也就是防止你在lambda内部使用的外层局部变量被外层代码修改了,但是lambda内部无法同步。
使用final属性的属性,初始化后就不能修改,可以理解该属性读改。
Java提示必须制定final才行:
public static void main(String[] args) { int[] nums = new int[]{1,2,4}; int num = 1; //外部发生修改 num--; nums = new int[]{1}; Thread thread= new Thread(()->{ nums[0] = 0; int result = num -1; }); }
复制错误复制成功
lambda函数的本质
没有用过lambda表达式的,你应该也过匿名函数,lambda表达式用的本质就是一个匿名函数,再合理一点,一个函数式接口实现的实例。
是一个接口的实现,那么外部变量是如何传递的?
很条件是通过构造器
为什么不是普通方法和构造器?因为拉姆达是要说明参数的,但不能是任意的,它的参数列表需要依靠接口的构造器而定。
lambda表达式实例化的时候,编译器会创建一个新的类文件(想一下你是不是在工程编译之后公开Main$1.class
的文件),该文件就是lambda实例化的类的字节码文件,在该文件中,编译器帮我们创建了一个构造器,该构造器的入参中就包含了你要使用的外层局部变量,所以外层局部变量就通过lambda的构造器实例实例内部供其使用。
值传递和引用传递
你是通过构造器传参,构造器也是方法,
如果这个变量是基本类型,那可能是一个变量传递,你那个变量层代码修改了了,那lambda 内也可能就在这个变量上,就可以找到外面的问题了。
这也是官方为了避免误会,这实际上是一个副本,不是原来的值
我外部一发生改变,匿名函数,lambda函数内就会报错:
public static void main(String[] args) { int num = 1; //外部发生修改 num--; Thread thread= new Thread(()->{ int result = num - 1; //报错 }); }
复制错误复制成功
如果是引用传递,实际上并不存在这个问题了,因为finalkey只是维护引用的地址,而不会维护引用的对象内部的属性值,这样是可以的:
public static void main(String[] args) { int[] nums = new int[]{1,2,4}; //外部发生修改 nums[0] = 0; // nums = new int[]{1}; 这样是会报错的,nums地址发生改变 Thread thread= new Thread(()->{ nums[0] = 1; }); }
复制错误复制成功
但如果nums
这个地址发生了改变,仍然会报错,就和表面的值传递一个链接了。
lambda 只是声明,不代表执行
是lambda还是匿名内部类,在写lambda表达式的时候,是不会直接去执行这个lambda表达式的,lambda只是一种,声明变量一样,你声明一个int x;声明,可能在很多行代码之后去调用这个lambda表达式才执行
上面的例子继续讲:
public static void main(String[] args) { int num = 1; Thread thread= new Thread(()->{ //3 int result = num - 1; //报错 }); thread.start(); //6 num -- ; //7 }
复制错误复制成功
这里的第3行是个lambda的声明,不能马上执行。
第6行启动线程,才是执行这个lambda
可能修改了第 7 行先执行,变成了把0
你的lambda 内的值,然后把你的lambda 内的值1
,这样导致了数据不同步的问题,这也解释了一个开头的说:变量可能会引入并发问题。
扫VX 领Java资料,前端,测试,python等等资料都有