提交 1071982c authored 作者: 邓砥奕's avatar 邓砥奕

更新笔记

上级 c73f8598
......@@ -4,19 +4,23 @@
Java8之前Lambda和匿名内部类引用外部局部变量必须声明为final类型,Java8之后引用的局部变量必须为final或effectively final.如果该局部变量在定义赋值后没有被修改,则认为该变量为effectively final类型,如果被赋值或修改则无法通过编译。因此在引用外部局部变量时,不能进行修改。
#### 二、为什么必须为final或effectively final
#### 二、不能修改原因分析
1.引入的局部变量是拷贝,不能改变原本的值。由于Lambda和匿名内部类有可能在另外一个线程执行无法访问外部局部变量,因此jvm通过它们的构造方法将需要引用的局部变量传入进去。通过构造方法传递的只是局部变量的一个拷贝,因此当外部或者内部类修改这个变量时,对方都不知道该变量的修改。既然改变不了该变量的值,为了保证变量的安全,所以在Java中索性直接不允许修改引用的局部变量,编译时强制该变量必须为final或者effectively final类型。相当于直接告诉使用者:这个局部变量在表达式内部不能改动,在外部也不要改动了。
1.引入的局部变量是拷贝,不能改变原本的值。由于Lambda和匿名内部类有可能在另外一个线程执行无法访问外部局部变量,因此jvm通过它们的构造方法将需要引用的局部变量传入进去。通过构造方法传递的只是局部变量的一个拷贝,因此当外部或者内部类修改这个变量时,对方都不知道该变量的修改。为了保证变量的安全,所以在Java中索性直接不允许修改引用的局部变量,编译时强制该变量必须为final或者effectively final类型。相当于直接告诉使用者:这个局部变量在表达式内部不能改动,在外部也不要改动了。
2.局部变量和内部类对象的生命周期不同。当调用该方法时,该方法中的局部变量在栈中被创建,是线程私有的。可能该方法调用结束并且释放了栈中的变量,但是内部类却还没有执行完,此时就找不到该局部变量了。为了保证变量的安全访问,内部类会自动通过构造函数的参数创建一个final类型的拷贝值。由于是拷贝值,因此一但一方修改了局部变量的值会导致另外一方对该变量的更改不可见,从而出现问题。
3.并发的安全性问题。当多个线程并发执行Lambda或匿名内部类中的代码时,不能保证外部成员变量的原子性,从而可能导致变量值的安全性问题。当引用自由变量时,考虑并发的安全性,也必须为final或effectively final。
以上分析的原因只是表面的一些原因,修改局部变量的值也不会导致出现大的编译问题,为什么jvm直接禁止修改局部变量的值呢?
java最开始为了方便使用创建了八大基本数据类型,这些数据类型直接存储在栈中,不需要在堆中分配地址存储数据。因此内部类引入基本数据类型时,会在自己的栈中创建该变量。它们对应的封装类是不可变类,对应vaule值是final类型的。因此像更改Integer的值,实际上会new一个新的Integer对象存储新的值,再修改引用的地址为新的Integer地址。修改String也会new一个新的String对象。它们都只拷贝了值,属于深拷贝,对于深拷贝的值的修改并不能改变拷贝前的值。所以jvm无法实现修改这些类型的局部变量的值,因此就规定引用的局部变量必须为final或effectively final。
#### 三、有哪些变量可变
1.定义为内部类的局部变量
2.定义为外部类的成员变量:成员变量存储在堆上,可以正常访问
2.定义为外部类的成员变量:成员变量的值存储在堆上,可以通过地址访问修改
3.定义为Atomic原子类型:保证了变量的可见性和原子性。
......@@ -32,3 +36,7 @@ Java8之前Lambda和匿名内部类引用外部局部变量必须声明为final
2.当传递的是引用类型,包括数组、对象以及接口时,传递的是对象在内存中的地址的拷贝。因此修改这些对象的值会影响原值。
#### 五、浅拷贝和深拷贝
深拷贝指的是对值进行拷贝,浅拷贝指的是对值的地址进行拷贝,指向同一个值。对于基本数据类型和String,只能进行深拷贝直接拷贝的它们的值。对于引用类型,主要进行浅拷贝,浅拷贝指的是拷贝的是对象的地址,修改拷贝后的值会影响到拷贝前的值。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论