阳光沙滩
让学习编程变得简单
为什么Java匿名内部内使用局部变量需要加final?而访问成员变量却不用加final呢?
发表于 2020-05-13    阅读次数 188

前置知识

内存数据区域(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回收有一定的规则,对象没有被引用了才会去回收。

比如说标记-清除算法,标记-压缩算法,复制算法,分代收集算法。

所以确保了数据的一致性。