之前在做第一个安卓项目时,在使用TimerTask和使用Thread创建新线程的时候,发现如果在匿名方法中对局部变量进行修改的时候会报错,但是可以访问局部变量,而使用成员变量的时候就不会报错。不过,如果将其写成下面就不会报错了,但是问题又来了,明明将变量声明成了final类型,为什么可以修改其值呢?
void myThread() {
final int[] i = {0};
Thread thread = new Thread(()-> i[0]++);
thread.start();
}
我们需要解决以下几个问题:
- 为什么在匿名方法中可以修改成员变量而不可以修改局部变量?
- 为什么匿名方法中只能访问final类型的局部变量?
- 为什么final数组的值可以被修改?
在匿名方法中使用的局部变量为什么只能是final
原因是Java设计者想要匿名函数或者Lambda函数内部外部保持数据一致性,在匿名方法中使用局部变量时,内部类并不是真正地使用该变量,而是在匿名方法中复制一个副本,由于是副本,所以即使是在匿名方法中修改了该变量,局部变量也不会发生变化,所以匿名方法使用的局部变量只能是final类型的变量。
注:在Java8以前,访问任何局部变量都要加final关键字,而在Java8之后,只要不涉及修改局部变量的操作,就可以不用加final关键字。
但是,对于成员变量来说,匿名方法会保存外部类的引用,因而内部类中对任何字段的修改都回真实的反应到外部类实例本身上,所以不需要用final来修饰它。具体可以参考该博客:https://www.cnblogs.com/qq78292959/p/3745348.html
Final arrays
如果非要对局部变量进行修改怎么办,那就只能用到上面的final数组方法。我们都知道,final类型的数据是不可以被修改的,但是为什么此处的final数组的值被修改了呢?
下面是一个静态常量:
public final int[] number = {1, 2, 3};
final关键字能保证数据不可以被修改对于基本类型来说这点是成立的,但对于数组来说就不成立了,因为数组和类以及接口一样,是引用类型,引用数据类型实际上只储存了一个指针,final只能保证其引用不变,即这个指针不变,但是数组中的对象的值还是可以改变的。而且,引用数据类型的数据是储存在堆内存中的,匿名方法可以共享。
注意:正是由于final数组的这个特性,所以在需要定义一个常量数组的时候,不要使用该方法,这样定义依然是会被修改值的。
文章开始的代码可以重写Thread类,定义Thread类的成员变量来实现相同的功能。
private void myThread(){
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
super.run();
i++;
System.out.println(i);
}
}
MyThread myThread = new MyThread();
myThread.start();
}
总结
- 在匿名方法中使用的局部变量只能是fianl类型的。
- Java8以后,只要不对局部变量修改,局部变量即使不加final关键字也可以被匿名方法访问。
- 如果匿名方法要修改局部变量,可以将变量定义成final数组的形式。
- 如果要final发挥常量作用,final数组要慎用。