为什么Java匿名内部内使用局部变量需要加final?而访问成员变量却不用加final呢?
前置知识
内存数据区域(Java内存结构)
在解析这个问题之前,得先有一些前置知识。
比如说,要知道什么是堆,什么是栈,什么是方法区,这数据区域存放什么内容?
可以参考这篇文章:
JVM内存运行时数据区域( Run-Time Data Areas)
栈里面存放方法内容
比如说以下代码:
public class App {
public static void main(String[] args) {
sayHello();
}
private static void sayHello() {
System.out.println("hello world");
sayBye();
}
private static void sayBye() {
System.out.println("good bye...");
doSum();
}
private static void doSum() {
int a = 10;
int b = 29;
int sum = a + b;
System.out.println("sum is -- > " + sum);
}
}
打个断点
第一个方法都压进栈里
这里每一个Item叫做栈帧,也就是frame
执行的时候就Pop出去,顶部的先出栈,执行完毕。
所以如果是递归的话,一直压栈,容易导致栈内存溢出。
final关键字
这个我相信大家都知道,因为面试常问
- final 修饰的变量值不能被修改,一般定义常量用此修饰
- final 修饰的类不能被继承,比如说String
- final 修饰的方法不可以被覆写
提个问题
final User user = new User("zhangsan",18);
用final修饰了一个成员,这个成员是User,里面有名称和年龄的属性。我可经修改它的年龄吗?
可以,只是你的user不能再指向其他对象。这个对象里的值还是可以修改的。所以,final修饰的对象,对象的属性是可以修改的。
案例
给我们的RecyclerView的Item设置点击事件。
@Override
public void onBindViewHolder(@NonNull InnerHolder holder,int position) {
TextView itemTv = holder.itemView.findViewById(R.id.left_category_tv);
if(mCurrentSelectedPosition == position) {
itemTv.setBackgroundColor(itemTv.getResources().getColor(R.color.colorEEEEEE,null));
} else {
itemTv.setBackgroundColor(itemTv.getResources().getColor(R.color.white,null));
}
final SelectedPageCategory.DataBean dataBean = mData.get(position);
itemTv.setText(dataBean.getFavorites_title());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(mItemClickListener != null && mCurrentSelectedPosition != position) {
//修改当前选中的位置
mCurrentSelectedPosition = position;
mItemClickListener.onLeftItemClick(dataBean);
notifyDataSetChanged();
}
}
});
}
我们给item设置了点击事件,而里面的匿名内部类使用到了方法里的局部变量
final SelectedPageCategory.DataBean dataBean = mData.get(position);
为什么要加final呢?
因为我们的方法调用时在栈里,出栈执行,就会释放掉了。
所以就没有dataBean 这个数据了。而创建的对象 ,在堆里,地址引用给了ItemView的onClickListener成员。
所以两者的声明周期不一样。dataBean 的声明周期在此方法执行完就释放了。
而匿名对象还在。为什么要加final呢?
前面我们说了,加了final修饰的变量则为常量,不可以改变其值。
而匿名内部类里用到的那个dataBean,是复制了一份到里面的方法里。这个打断点就知道了,可以看到当方法执行的时候,会在栈内存里有此变量。
有了final修饰,就是确保一致性。
为什么成员变量则不需要加final呢?
我们匿名内部类使用成员变量时,不需要加final,为什么呢?
我们的成员变量保存在哪里呢?我们创建对象的时候,会在堆内存里开新空间。堆内存什么时候回收,GC回收对吧。GC回收有一定的规则,对象没有被引用了才会去回收。
比如说标记-清除算法,标记-压缩算法,复制算法,分代收集算法。
所以确保了数据的一致性。