【Java面试】为什么匿名内部类只能访问外部类的final类型局部变量?

简介: 【Java面试】为什么匿名内部类只能访问外部类的final类型局部变量?

先来看一下下面一段代码

public class InnerClassTest {
    public static void main(String[] args) {
        int a = 10;
        new Service() {
            @Override
            public void method() {
                System.out.println("a=" + a);
            }
        }.method();
        a = 11;
    }
}
interface Service {
    public void method();
}

这段代码并不能通过编译,因为他会抱有如下异常:

这里发现,我的匿名内部类调用外部的局部变量的时候发生了报错,那么这个报错的原因是什么?

我们先来看解决这个报错的方法:

1:删掉下面的对a=11的修改,这意味着a这个值并没有被修改,是只读的

2:将a变量设定为final类

两种方法都能解决上面的问题,但是为什么我们使用外部的局部变量的时候我们需要它是未被修改的或者说为什么必须是final的?

答:

匿名内部类无法直接访问外部类方法中的局部变量,除非该变量被声明为final类型,是因为匿名内部类在实例化时会隐式地持有对外部类方法中的局部变量的引用。为了确保引用的可用性和一致性,Java编译器要求局部变量必须是final类型的。

当一个局部变量被声明为final时,Java编译器会在内存中创建一个拷贝,而不是直接引用原始变量。这样做的目的是为了避免匿名内部类中对外部局部变量的修改导致不一致的情况发生。

通过将局部变量声明为final,Java编译器确保了匿名内部类在获取局部变量的值时,能够获取到该变量的固定值。这样,即使外部方法调用已经结束,局部变量仍然可以正确地被匿名内部类所访问和使用。

需要注意的是,在Java 8之后,如果局部变量被显式声明为final,即使没有使用final关键字,同样可以在局部类或匿名内部类中访问。这是因为Java 8引入了"effectively final"的概念,即在变量被赋值后,没有再发生修改。在这种情况下,编译器会将其视为final类型的变量,从而允许在局部类或匿名内部类中访问该变量。这也就是为什么上面我们只要把a=11这一行代码删掉也可以通过运行的原因。

当然,如果你的JDK版本是7或者更早,那么就依旧会报错,如下:

面试官:为什么匿名内部类只能访问外部类的final类型局部变量?

我对这个问题的完整解释是这样子回答的

我:其实对于为什么需要使用final类型的外部局部变量,我的解释应该会倾向于生命周期的概念。

我们知道,匿名内部类的调用发生在方法中,方法创建时会创建一个栈帧,栈帧中保存的是我们的局部变量等信息,这个时候如果我们使用了匿名内部类,还会再堆中创建一个类,然后如果我们的这个匿名内部类使用了外部变量,而外部变量的创建是跟随方法的,如果方法结束,那么外部变量就要被回收消失,此时会出现生命周期的问题,也就是我们的匿名内部类还指向这个方法,并且内部类还没有被回收,因为堆内对象的回收需要的是垃圾回收器的工作而不是跟随方法,即使这个对象是通过这个方法才创建的。

因此此时就会出现外部变量消失的情况,而匿名内部类依旧存在于堆内存中并且对外部变量存有引用,为了解决这种生命周期不一致的问题,可以使用final关键字修改局部变量的生命周期,我们知道如果对局部变量使用final修饰,他就会在内存中留有一份数据。

当局部变量被声明为final时,它们在内存中会保留其值。在使用final修饰的局部变量时,其值在声明时被确定,并且不能再被修改。这样做的目的是为了确保在匿名内部类或其他类的方法中使用这些final变量时,它们的值保持不变。

在编译过程中,如果一个局部变量被匿名内部类或其他闭包引用,编译器会创建一个新的内部类,并将这些被引用的final局部变量的值传递给内部类的构造函数。因此,这些final局部变量的值将在内存中一直存在,直到内部类对象不再被引用,并由垃圾回收器回收。

值得注意的是,如果局部变量没有被匿名内部类或其他闭包引用,即使将其声明为final,它们在方法执行完毕后仍然会被销毁,不会一直保存在内存中。只有在有需要的情况下才将局部变量声明为final。


相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
27天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
67 14
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
1月前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
36 6
|
SQL 缓存 安全
Java高频面试题目
面试时面试官最常问的问题总结归纳!
152 0
JAVA高频面试题目集锦(6)
JAVA高频面试题目集锦(6)
147 0
JAVA高频面试题目集锦(6)
|
存储 安全 Java
JAVA高频面试题目集锦(5)
JAVA高频面试题目集锦(5)
189 0
JAVA高频面试题目集锦(5)
|
算法 安全 Java
JAVA高频面试题目集锦(4)
JAVA高频面试题目集锦(4)
112 0
JAVA高频面试题目集锦(4)
|
存储 Java 对象存储
JAVA高频面试题目集锦(3)
JAVA高频面试题目集锦(3)
122 0
JAVA高频面试题目集锦(3)
|
存储 缓存 算法
JAVA高频面试题目集锦(2)
JAVA高频面试题目集锦(2)
138 0
JAVA高频面试题目集锦(2)

热门文章

最新文章