欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

匿名内部类访问的局部变量为什么需要用final修饰?

程序员文章站 2022-04-11 18:44:15
...

前言

看到标题,有一种很熟悉的感觉,我相信只要有几个月开发经验的人,总会遇到一个问题:匿名内部类访问的局部变量,必须用final修饰,要不然编译不通过。很多人都知道需要加final,但是这是为什么呢?

原因

一句话解释:保持数据的一致性。

  • 基本数据类型:保持值的一致性
  • 引用类型:保持引用的一致性

匿名内部类访问局部变量,编译器会拷贝一份使用的值通过匿名内部类的构造方法传进来

如果方法中的局部变量或者匿名内部类中的该变量其中一个发生了变化,那么可能会导致程序运行的结果与预期不符

疑问

为什么匿名内部类不能直接引用局部变量呢?

如果直接引用:

匿名内部类访问的局部变量为什么需要用final修饰?

此方法被调用,在它的调用栈生成了一个变量,当这个方法运行结束的时候,退栈,局部变量也就死亡了,但有可能这个内部类的对象还在,这样就造成了这个匿名内部类的生命周期比这个方法还长,在匿名内部类里就无法访问该变量了,所以Java会复制一份过去,像这样:

匿名内部类访问的局部变量为什么需要用final修饰?

这样就解决了生命周期不一致的问题

但是为什么要加final呢?

如果不加final:

匿名内部类访问的局部变量为什么需要用final修饰?

无论是在方法中修改了局部变量的值,还是在匿名内部类中修改了局部变量的值,都会导致两边不一致的问题。

一人拿了一份文档,一模一样的,然后各自开始乱改,这肯定不行呀。

所以加了final,规定好了:基本数据类型的值不能变,引用类型的引用不能变。

备注:在Java8之后,匿名内部类使用局部变量的时候,不需要final修饰了,底层自己加了final,但是不允许重新赋值。

 

顺带再分享一些相关知识:

静态内部类和非静态内部类

静态内部类的实例化

// 内部类interface默认是静态的
public class MyViewHolder extends ViewHolder{
    ......

    public interface SendMessageListener {
        void onClick();
    }
}
 
// 比如在写RecyclerView时,我们常常写回调就可以这样:
ViewHolder.SendMessageListener listener = new ViewHolder.SendMessageListener() {
    void onClick()
}





// 我们常常写Bean的时候,会创建static的静态内部类
public class CommentBean {
    ...

    public static class ListItem {
        ...
    }
}

// 而实例化的时候就是这样写的:
public CommentBean.ListItem item = new CommentBean.ListItem();





静态内部类可以有静态成员
静态内部类只可以访问外部类的静态成员

非静态内部类的实例化

public class MyBean {
    ...
    public class Info {
        ...
    }
}


MyBean.Info bean = new MyBean().new Info();


// 由上可知:非静态内部类和匿名内部类是默认持有外部类的引用,所以可以直接访问外部类的成员

非静态内部类不能有静态成员
非静态内部类可以访问外部类的所有成员

匿名内部类的构造方法

匿名内部类的构造方法是编译器定义的,编译器编译的时候,匿名内部类会生成一个:外部类$x.class的文件,

public class MainDemo {

    public void run() {
        final String name = "pengboboer";

        new MyBean().new Info(){
            void onClick() {
                String myName = name;
            }
        };
    }
}

首先,Info要持有它外部类MyBean实例的引用

同时它也在非静态方法区,Info也会持有MainDemo的引用

同时Info要访问局部变量,所以也会复制拷贝一份给Info

public class MainDemo$1 {
    public MainDemo$1(MainDemo mainDemo, MyBean myBean, String name){
        ...
    }
}

总结