`
wenzongliang
  • 浏览: 449331 次
  • 性别: 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

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

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    7.8.1 发现问题:当实例变量和局部变量重名 177 7.8.2 经常深藏不露的this关键字 178 7.8.3 在方法中调用方法 179 7.9 构造方法(Constructor) 181 7.9.1 构造(Constructor)方法初探 181 7.9.2 如何使用构造...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    7.8.1 发现问题:当实例变量和局部变量重名 177 7.8.2 经常深藏不露的this关键字 178 7.8.3 在方法中调用方法 179 7.9 构造方法(Constructor) 181 7.9.1 构造(Constructor)方法初探 181 7.9.2 如何使用构造...

    Java 基础核心总结 +经典算法大全.rar

    《Java 基础核心总结》 Java 概述 什么是 Java2 Java 的特点Java 开发环境 JDK JRE Java 开发环境配置 Java 基本语法 数据类型基础语法运算符 Java 执行控制流程条件语句 if 条件语句 if...else 条件语句if....局部变量

    21天学通Java-由浅入深

    240 12.2.1 创建局部内部类 240 12.2.2 在局部内部类中访问外部类成员变量 240 12.2.3 在局部内部类中访问外部类的局部变量 241 12.2.4 静态方法中的局部内部类 243 12.3 静态内部类 244 12.3.1 创建静态内部类 244 ...

    【05-面向对象(下)】

    •当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在该 名字的局部变量,就使用该变量,如果不存在,则到该方法所在的内部类中查找是否存在该名字的属性,如果...

    day022-jdk8新特性和lambda表达式 代码和笔记.rar

    1. lambda: 1. 概念:就是对函数式接口匿名内部类的简写 ... 7、lambda表达式若访问了局部变量,则局部变量必须是final的, 若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错;

    疯狂JAVA讲义

    学生提问:为什么静态内部类实例方法也不能访问外部类的实例属性呢? 207 学生提问:接口里是否能定义内部接口? 208 6.7.3 使用内部类 208 学生提问:既然内部类是外部类的成员,是否可以为外部类定义子类,在...

    Java2实用教程.rar

    4 2 3成员变量和局部变量 4 2 4方法 4 2 5方法重载 4 2 6构造方法 4 2 7类方法和实例方法 4 2 8值得注意的问题 4 3对象 4 3 1创建对象 4 3 2使用对象 4 3 3于象的引用和实体 4 3 4参数传值 4 4static关键字 4 4 1...

    corejava培训文档

    6.3. 成员变量和局部变量 6.4. 成员方法 6.5. This关键字 6.6. 访问控制符 6.7. 构造方法 6.8. 数据和方法的隐藏―――封装 6.9. 方法的重载 7. 六 高级语言特性 7.1. 封装 (encapsulation) 7.2. 继承 ...

    java 面试题 总结

    java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类 3、int 和 Integer 有什么区别 Java 提供两种不同的类型:引用类型和原始类型(或内置...

    java基础案例与开发详解案例源码全

    8.1.2 局部内部类206 8.1.3 静态内部类209 8.1.4 匿名内部类211 8.2 对象包装器213 8.3 装箱和拆箱216 8.4 本章习题218 第9章 9.1 String类220 9.1.1 字符串常量221 9.1.2 字符串对象操作224 9.1.3 字符串对象修改...

    java学习笔记 初学者必读

    6.3. 成员变量和局部变量 6-12 6.4. 成员方法 6-12 6.5. This关键字 6-13 6.6. 访问控制符 6-13 6.7. 构造方法 6-13 6.8. 数据和方法的隐藏―――封装 6-14 6.9. 方法的重载 6-15 7. 六•高级语言特性 7-15 7.1. ...

    【后端】java基础(5.4)java高级基础之java的修饰符——非访问控制符

    上章讲了访问控制符,这章开始讲非...局部变量 static 静态的/全局的 √(只可修饰内部类) √ √ × final 最终的 √ √ √ √ abstract 抽象的 √ √ √ × 首先我们来说说static: 被static修饰的方法我们称为静态方

    corejavaNoteBook

    6.3. 成员变量和局部变量 6-12 6.4. 成员方法 6-12 6.5. This关键字 6-13 6.6. 访问控制符 6-13 6.7. 构造方法 6-13 6.8. 数据和方法的隐藏―――封装 6-14 6.9. 方法的重载 6-15 7. 六•高级语言特性 7-15 7.1. ...

    javaSE代码实例

    15.2.2 局部变量与局部内部类 329 15.2.3 静态方法中的局部内部类 331 15.3 静态内部类 332 15.3.1 语法规则 332 15.3.2 创建静态内部类的对象 332 15.3.3 静态/非静态内部类的区别 333 15.4 匿名内部...

Global site tag (gtag.js) - Google Analytics