在我的上一篇博客 深入理解Java中为什么内部类可以访问外部类的成员 中, 通过使用javap工具反编译内部类的字节码, 我们知道了为什么内部类中可以访问外部类的成员, 其实是编译器在编译内部类的class文件时,偷偷做了一些工作, 使内部类持有外部类的引用, 并且通过在构造方法上添加参数注入这个引用, 在调用构造方法时默认传入了外部类的引用。 我们之所以感到疑惑, 就是因为编译器使用的障眼法。当我们把字节码反编译出来之后, 编译器的这些小伎俩就会清清楚楚的展示在我们面前。 感兴趣的朋友可以移步到上一篇博客, 博客链接: http://blog.csdn.net/zhangjg_blog/article/details/20000769
在本文中, 我们要对定义在方法中的内部类进行分析。 和上一篇博客一样, 我们还是使用javap工具对内部类的字节码进行解剖。 并且和上一篇文章进行对比分析, 探究定义在外部类方法中的内部类和定义在外部类中的内部类有哪些相同之处和不同之处。 这篇博客的讲解以上一篇为基础, 对这些知识点不是很熟悉的同学, 强烈建议先读上一篇博客。 博客的链接已经在上面给出。
在平时写代码的过程中, 我们经常会写类似下面的代码段:
public class Test { public static void main(String[] args) { final int count = 0; new Thread(){ public void run() { int var = count; }; }.start(); } }
public class Test { public static void main(String[] args) { final int count = 0; //在方法中定义一个内部类 class MyThread extends Thread{ public void run() { int var = count; } } new MyThread().start(); } }
让我们仔细观察上面的代码都有哪些“奇怪”的行为:
1 在外部类的main方法中有一个局部变量count, 并且在内部类的run方法中访问了这个count变量。 也就是说, 方法中定义的内部类, 可以访问方法中的局部变量(方法的参数也是局部变量);
2 count变量使用final关键字修饰, 如果去掉final, 则编译失败。 也就是说被方法中的内部类访问的局部变量必须是final的。
由于我们经常这样做, 这样写代码, 久而久之养成了习惯, 就成了司空见惯的做法了。 但是如果要问为什么Java支持这样的做法, 恐怕很少有人能说的出来。 在下面, 我们就会分析为什么Java支持这种做法, 让我们不仅知其然, 还要知其所以然。
public class Outer { void outerMethod(){ final String localVar = "abc"; /*定义在方法中的内部类*/ class Inner{ void innerMethod(){ String a = localVar; } } } }
Constant pool: #1 = Class #2 // Outer$1Inner #2 = Utf8 Outer$1Inner #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 this$0 #6 = Utf8 LOuter; #7 = Utf8 <init> #8 = Utf8 (LOuter;)V #9 = Utf8 Code #10 = Fieldref #1.#11 // Outer$1Inner.this$0:LOuter; #11 = NameAndType #5:#6 // this$0:LOuter; #12 = Methodref #3.#13 // java/lang/Object."<init>":()V #13 = NameAndType #7:#14 // "<init>":()V #14 = Utf8 ()V #15 = Utf8 LineNumberTable #16 = Utf8 LocalVariableTable #17 = Utf8 this #18 = Utf8 LOuter$1Inner; #19 = Utf8 innerMethod #20 = String #21 // abc #21 = Utf8 abc #22 = Utf8 a #23 = Utf8 Ljava/lang/String; #24 = Utf8 SourceFile #25 = Utf8 Outer.java #26 = Utf8 EnclosingMethod #27 = Class #28 // Outer #28 = Utf8 Outer #29 = NameAndType #30:#14 // outerMethod:()V #30 = Utf8 outerMethod #31 = Utf8 InnerClasses #32 = Utf8 Inner { final Outer this$0; flags: ACC_FINAL, ACC_SYNTHETIC Outer$1Inner(Outer); flags: Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #10 // Field this$0:LOuter; 5: aload_0 6: invokespecial #12 // Method java/lang/Object."<init>":()V 9: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this LOuter$1Inner; void innerMethod(); flags: Code: stack=1, locals=2, args_size=1 0: ldc #20 // String abc 2: astore_1 3: return LineNumberTable: line 10: 0 line 11: 3 LocalVariableTable: Start Length Slot Name Signature 0 4 0 this LOuter$1Inner; 3 1 1 a Ljava/lang/String; }
...... ...... #13 = Utf8 LOuter; #14 = Utf8 outerMethod #15 = String #16 // abc #16 = Utf8 abc ...... ......
public class Outer { void outerMethod(){ final int localVar = 1; /*定义在方法中的内部类*/ class Inner{ void innerMethod(){ int a = localVar; } } } }
{ final Outer this$0; flags: ACC_FINAL, ACC_SYNTHETIC Outer$1Inner(Outer); flags: Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #10 // Field this$0:LOuter; 5: aload_0 6: invokespecial #12 // Method java/lang/Object."<init>":()V 9: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this LOuter$1Inner; void innerMethod(); flags: Code: stack=1, locals=2, args_size=1 0: iconst_1 1: istore_1 2: return LineNumberTable: line 10: 0 line 11: 2 LocalVariableTable: Start Length Slot Name Signature 0 3 0 this LOuter$1Inner; 2 1 1 a I }
iconst_1
final String localVar = "abc";
final int localVar = 1;
public class Outer { void outerMethod(){ final String localVar = getString(); /*定义在方法中的内部类*/ class Inner{ void innerMethod(){ String a = localVar; } } new Inner(); } String getString(){ return "aa"; } }
Constant pool: #1 = Class #2 // Outer$1Inner #2 = Utf8 Outer$1Inner #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 this$0 #6 = Utf8 LOuter; #7 = Utf8 val$localVar #8 = Utf8 Ljava/lang/String; #9 = Utf8 <init> #10 = Utf8 (LOuter;Ljava/lang/String;)V #11 = Utf8 Code #12 = Fieldref #1.#13 // Outer$1Inner.this$0:LOuter; #13 = NameAndType #5:#6 // this$0:LOuter; #14 = Fieldref #1.#15 // Outer$1Inner.val$localVar:Ljava/la ng/String; #15 = NameAndType #7:#8 // val$localVar:Ljava/lang/String; #16 = Methodref #3.#17 // java/lang/Object."<init>":()V #17 = NameAndType #9:#18 // "<init>":()V #18 = Utf8 ()V #19 = Utf8 LineNumberTable #20 = Utf8 LocalVariableTable #21 = Utf8 this #22 = Utf8 LOuter$1Inner; #23 = Utf8 innerMethod #24 = Utf8 a #25 = Utf8 SourceFile #26 = Utf8 Outer.java #27 = Utf8 EnclosingMethod #28 = Class #29 // Outer #29 = Utf8 Outer #30 = NameAndType #31:#18 // outerMethod:()V #31 = Utf8 outerMethod #32 = Utf8 InnerClasses #33 = Utf8 Inner { final Outer this$0; flags: ACC_FINAL, ACC_SYNTHETIC Outer$1Inner(Outer, java.lang.String); flags: Code: stack=2, locals=3, args_size=3 0: aload_0 1: aload_1 2: putfield #12 // Field this$0:LOuter; 5: aload_0 6: aload_2 7: putfield #14 // Field val$localVar:Ljava/lang/String; 10: aload_0 11: invokespecial #16 // Method java/lang/Object."<init>":()V 14: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 15 0 this LOuter$1Inner; void innerMethod(); flags: Code: stack=1, locals=2, args_size=1 0: aload_0 1: getfield #14 // Field val$localVar:Ljava/lang/String; 4: astore_1 5: return LineNumberTable: line 10: 0 line 11: 5 LocalVariableTable: Start Length Slot Name Signature 0 6 0 this LOuter$1Inner; 5 1 1 a Ljava/lang/String; }
Outer$1Inner(Outer, java.lang.String);
6: aload_2 7: putfield #14 // Field val$localVar:Ljava/lang/String;
0: aload_0 1: getfield #14 // Field val$localVar:Ljava/lang/String;
在源代码层面上, 它的工作方式有点像这样: (注意, 下面的代码不符合Java的语法, 只是模拟编译器的行为)
public class Outer { void outerMethod(){ final String localVar = getString(); /*定义在方法中的内部类*/ class Inner{ /*下面两个成员变量都是编译器自动加上的*/ final Outer this$0; //指向外部类对象的引用 final String val$localVar; //被访问的外部类方法中的局部变量的值 /*构造方法, 两个参数都是编译器添加的*/ public Inner(Outer outer, String outerMethodLocal){ this.this$0 = outer; this.val$localVar = outerMethodLocal; super(); } void innerMethod(){ /*将对外部类方法中的变量的访问, 转换成对当前对象的成员变量的访问*/ //String a = localVar; String a = val$localVar; } } /*在外部类方法中创建内部类对象时, 传入相应的参数, 这两个参数分别是当前外部类的引用, 和当前方法中的局部变量*/ //new Inner(); new Inner(this, localVar); } String getString(){ return "aa"; } }
public class Outer { void outerMethod(){ final int localVar = getInt(); /*定义在方法中的内部类*/ class Inner{ void innerMethod(){ int a = localVar; } } new Inner(); } int getInt(){ return 1; } }
如果这个局部变量是引用数据类型时, 拷贝外部类方法中的引用值给内部类对象的成员变量, 这样的话, 他们就指向了同一个对象。 代码示例和运行时的内存布局如下:
public class Outer { void outerMethod(){ final Person localVar = getPerson(); /*定义在方法中的内部类*/ class Inner{ void innerMethod(){ Person a = localVar; } } new Inner(); } Person getPerson(){ return new Person("zhangjg", 30); } }
由于这两个引用变量指向同一个对象, 所以通过引用访问的对象的数据是一样的, 由于他们都不能再指向其他对象(被final修饰), 所以可以保证内部类和外部类数据访问的一致性。
深入理解为什么Java中方法内定义的内部类可以访问方法中的局部变量,布布扣,bubuko.com
深入理解为什么Java中方法内定义的内部类可以访问方法中的局部变量
原文:http://blog.csdn.net/zhangjg_blog/article/details/19996629