`
wenzongliang
  • 浏览: 466610 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Java内部类访问局部变量时的final问题

 
阅读更多

JAVA用了也快三年了,内部类访问局部变量的情况也没少遇到。也一直知道要给变量加个final修饰符,不然通过不了编译。但一直也没深究过为什么要加。昨天好奇的上网查了下,并翻阅了下相关的书籍(Core Java 8th),终于算是搞明白了,在这里简单说明下。

 

说先我们来看一段示例代码:

01 public void start(int interval, final boolean beep) {
02
03     // Inner Class
04     class TimePrinter implements ActionListener {
05
06         @Override
07         public void actionPerformed(ActionEvent event) {
08             Date now = new Date();
09             System.out.println(“At the tone, the time is “ + now);
10             if (beep) {
11                 Toolkit.getDefaultToolkit().beep();
12             }
13         }
14     }
15     //
16     ActionListener listener = new TimePrinter();
17     Timer t = new Timer(interval, listener);
18     t.start();
19 }

 

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

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

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

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

 

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

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

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

 

 

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

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

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

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

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

分享到:
评论

相关推荐

    Java中局部内部类可以访问它所在方法中定义的final修饰的局部变量的合理解释.doc

    在代码中,我们可以看到,当我们尝试访问局部变量 a 时,编译器会报错。这是因为 JVM 不会将没有 final 修饰的局部变量复制给局部内部类。因此,当我们尝试访问 a 时,编译器不知道如何处理该变量,因为它已经被释放...

    局部内部类和匿名内部类使用局部变量为什么要final1

    2. **内部类的实现机制**:Java内部类的实现方式使得它们能够访问外部类的非静态成员,因为它们持有对外部类实例的引用(`Outer.this`)。对于局部变量,情况类似,Java实际上将这些变量作为参数传递给内部类的构造...

    局部变量用final的讨论

    2. **匿名内部类与final**:在Java中,如果一个局部变量要在匿名内部类中使用,那么它必须是`final`或effectively final(实际上相当于只赋值一次)。这是因为内部类可以访问外部类的final或effectively final变量,...

    Java成员变量类变量局部变量的区别共5页.pdf.zip

    Java编程语言中有三种主要的变量类型:成员变量(也称为实例变量)、类变量(也称为静态变量)和局部变量。理解这些变量之间的区别是掌握Java基础的重要部分,这对于编写高效、可维护的代码至关重要。 成员变量是...

    java内部类详解

    由于其局部性,局部内部类可以访问方法内的所有局部变量和参数,但这些变量必须是 final 或 effectively final。局部内部类不能声明为 static,也不能被外部类访问,除非通过方法返回。 4. 匿名内部类(Anonymous ...

    Java内部类总结

    - **局部内部类**:是在一个方法或构造器内部定义的类,可以访问该方法或构造器内的局部变量,但这些局部变量必须声明为`final`。 - **匿名内部类**:没有名字的内部类,通常用于实现接口或继承类时简化代码,主要...

    Java内部类访问规则.doc

    - 局部内部类可以直接访问外部类的成员,并且可以访问该方法中的局部变量,但这些局部变量必须是最终的(`final`)或者本质上不可变的。 **5. 匿名内部类:** - 匿名内部类是没有名称的内部类,通常用于实现接口或...

    java内部类的讲解

    它们可以访问该方法中的局部变量,但这些变量必须是final的。本地内部类的作用域仅限于定义它的方法或块。 4. **匿名内部类(Anonymous Inner Classes)**:匿名内部类没有名字,它们通常用于实现接口或继承类。在...

    java 内部类使用(内部匿名类)

    这种内部类的生命周期仅限于该方法的执行,可以访问方法的局部变量和参数,但对这些局部变量的访问有一些限制,它们必须是final或者等效于final的。 4. **局部内部类**:定义在方法、块或者构造器内部的类称为局部...

    JAVA内部类总结

    1. **访问权限**:局部内部类可以访问方法内的局部变量,但这些变量必须声明为final。 2. **实例化**:局部内部类只能在其定义的方法或构造函数内部实例化。 3. **示例**: ```java public void method() { final...

    Java基础权限控制与final,内部类

    当你声明一个引用类型变量为final时,你不能将它指向另一个对象,但是你可以修改对象内部的成员变量。这意味着,引用本身不能改变,但你可以改变引用所指向对象的内容。 Java中的内部类是一种定义在另一个类内部的...

    java 匿名内部类的使用规范

    如果匿名内部类需要访问局部变量,那么这个局部变量必须是final的或者实际上等效于final。这是因为匿名内部类可以异步执行,它们可能在局部变量的生命周期结束后仍然需要访问这些变量。 4. **单例模式中的应用** ...

    java代码笔记2010-06-01:Java内部类 静态内部类 局部内部类 明明内部类;StringBuffer reverse的使用;

    这使得局部内部类可以访问它所在范围内的所有变量,包括局部变量,但这些变量必须是final的。局部内部类主要用来实现某个特定功能,其生命周期与包含它的方法或块相同。 4. **明明内部类**: "明明内部类"可能是指...

    Java内部类应用详解

    - **访问限制**:只能访问定义该内部类的方法内的最终(final)局部变量。 示例代码如下所示: ```java class Outer { public void doSomething() { final int i = 10; class Inner { public void seeOuter() ...

    Java内部类.pdf

    - 局部内部类:定义在方法或者作用域块中,不能有访问权限修饰符,只能访问方法中被final修饰的局部变量。 - 匿名内部类:没有类名的局部内部类,通常用在需要创建一个对象时,且仅需使用一次的场合。 - 静态内部类...

    Java 干货之深入理解Java内部类(学习资料)

    匿名内部类可以访问 final 或 effectively final 的局部变量。创建匿名内部类的方式是通过继承一个类或实现一个接口,并在实例化时提供初始代码块。 4. 静态内部类(嵌套类) 静态内部类与普通成员内部类的区别在于...

    java内部类使用例子

    局部内部类不能访问外部类的非final局部变量,但可以直接访问方法参数。 接下来,我们来看看匿名内部类,这是内部类的一个特殊形式,它没有名称,通常用于实现接口或抽象类的简短回调。在标签"AnonymousInner"提示...

Global site tag (gtag.js) - Google Analytics