JAVA内部类访问局部变量时为什么要加final关键字

本文解析了Java内部类如何访问外部类的私有变量及局部变量,并详细解释了为何在访问局部变量时需要使用final关键字。
说先我们来看一段示例代码
public void start(int interval, final boolean beep) {

     // Inner Class
     class TimePrinter implements ActionListener {

         @Override
         public void actionPerformed(ActionEvent event) {
             Date now = new Date();
             System.out.println(“At the tone, the time is “ + now);
             if (beep) {
                 Toolkit.getDefaultToolkit().beep();
            }
         }
     }
     //
     ActionListener listener = new TimePrinter();
     Timer t = new Timer(interval, listener);
     t.start();

我们在start函数中定义了一个内部类TimePrinter,其中访问了函数的局部变量beep

为了说明内部类访问局部变量为什么要加final关键字,我们先来看一下JAVA对内部类的实现。

假设上述代码中start函数所在的类的名称为TalkingClock。则编译上述代码的时候,JAVA编译器会把TimePrinter内部类编译为一个独立的class文件。其形式如下(类名为:外部类$内部类):

class TalkingClock$1TimePrinter {
     // 添加的构造函数,参数为外部类对象的引用和该内部类访问的局部变量的引用(这里为boolean类型)
     TalkingClock$1TimePrinter(TalkingClock, boolean);
     // 内部类原有的函数
    public void actionPerformed(java.awt.event.ActionEvent);
     // 局部变量的引用
     final boolean val$beep;
    // 外部类对象的引用
     final TalkingClock this$0;

通过上述类的定义,我们可以看出内部类在构造的时候,会被编译器自动传入外部类对象的一个引用,同时也会传入内部类访问的局部变量的引用,这也就解释了内部类对象为什么可以访问外部类的成员变量和函数还有局部变量了。但是由于这些工作是在编译时进行的,JAVA虚拟机并没有什么所谓的内部类的概念,在JAVA虚拟机看来,该内部类和外部类是两个独立的class文件。我们知道,一个类的私有函数和成员变量是不能被其他类访问的。那么内部类又是如何访问外部类的私有成员变量和函数呢?

我们假设外部类中有一个私有的int型的变量counter。我们想在内部类中访问它。其实在编译的时候,为了内部类可以访问外部类的私有变量,JAVA编译器还偷偷做了一些额外的工作。编译器除了上文提到的会生成一个内部类类,同时还会修改我们的外部类代码。其修改如下:

 class TalkingClock {
     // 编译器自动添加的函数,用来访问私有成员变量counter
     static int access$0(TalkingClock);
     // 原有的函数
     public void start();
     // 私有成员变量
     private int counter
 }

可以看出,为了访问counter私有成员变量,编译器偷偷的为我们添加了一个access$0的静态函数,它接收一个TalkingClock对象的引用,并返回该对象内的coutner的值。并且编译器会把我们在内部类中用到counter的地方都替换为TalkingClock.access$0(this$0)

好了,现在我们已经了解了内部类的实现机制,那我们最后来看一下访问局部变量为什么要求局部变量添加final关键字。

还是以我们最开始的那个start函数为例。我们来看一下该函数的可能的执行过程:

如果beep变量不被标注为final,那么就意味着我们可以随时修改beep的值。假设我们在创建了TimePrinter对象后修改了beep的值,那么这时我们的内部类所看到的beep的值还是之前通过构造函数传递进去的老值,这样就导致内部类和外部函数对beep值“认识”的不一致。所以final关键字的目的就是为了保证内部类和外部函数对变量“认识”的一致性。

结束语:这个内部类final的问题从最开始学JAVA时就遇到了,期间也有想过为什么要加,但最终都没有深究,就理所当然的认为是规定了。其实这是一个很不好的习惯。学习东西就要“知其然,知其所以然”,一知半解最是害人。希望大家都可以引以为鉴。




### 三级标题:局部内部类和匿名内部类访问 final 变量的原因 Java 中的局部内部类和匿名内部类访问局部变量,要求这些变量必须是 `final` 或“有效 final”(effectively final)。这一限制的主要原因与作用域、生命周期以及数据一致性有关。 局部内部类和匿名内部类的生命周期可能比定义它们的方法更长。例如,一个方法中定义的内部类对象可能在方法执行结束后仍然存在,并且可能被其他线程访问。在这种情况下,如果局部变量不是 `final` 的,那么该变量可能会在方法结束后被销毁,导致内部类访问到无效的数据。为了避免这种潜在的不一致性,Java 要求这些变量必须是 `final`,以确保它们在方法执行期间不会发生变化,并且可以在内部类中安全地使用[^1]。 此外,局部变量的作用域仅限于声明它们的代码块内。当内部类试图访问这些变量,由于它们的作用域限制,内部类实际上无法直接访问这些局部变量。通过将这些变量声明为 `final`,Java 允许内部类捕获这些变量的值,并在内部类的实例中保存它们的一个副本,从而实现跨作用域的访问[^2]。 在 Java 8 及以后版本中,引入了“有效 final”的概念,即如果一个局部变量在初始化后从未被修改过,即使没有显式地使用 `final` 关键字修饰,也可以被视为 `final` 变量。这意味着开发者可以在某些情况下省略 `final` 关键字,而编译器会自动将其视为 `final` 处理,这提高了代码的简洁性和可读性[^4]。 下面是一个简单的代码示例,展示了局部内部类如何访问 `final` 变量: ```java public class Test { public static void main(String[] args) { final int num = 10; // 必须是 final 或 effectively final class LocalInner { void display() { System.out.println("Value of num: " + num); } } LocalInner li = new LocalInner(); li.display(); } } ``` 在这个例子中,局部变量 `num` 被声明为 `final`,这样局部内部类 `LocalInner` 就可以安全地访问它。如果尝试修改 `num` 的值,则会导致编译错误,因为这会违反内部类只能访问不可变变量的原则。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值