昨天有一个比较爱思考的同事和我提起一个问题:为什么匿名内部类使用的局部变量和参数需要final修饰,而外部类的成员变量则不用?对这个问题我一直作为默认的语法了,木有仔细想过为什么(在分析完后有点印象在哪本书上看到过,但是就是没有找到,难道是我的幻觉?呵呵)。虽然没有想过,但是还是借着之前研究过字节码的基础上,分析了一些,感觉上是找到了一些答案,分享一下;也希望有大牛给指出一些不足的地方。
假如我们有以下的代码:
interface Printer { public void print(); } class MyApplication { private int field = 10; public void print(final Integer param) { final long local = 100; final long local2 = param.longValue() + 100; Printer printer = new Printer() { @Override public void print() { System.out.println("Local value: " + local); System.out.println("Local2 value: " + local2); System.out.println("Parameter: " + param); System.out.println("Field value: " + field); } }; printer.print(); } } |
这里因为param要在匿名内部类的print()方法中使用,因而它要用final修饰;local/local2是局部变量,因而也需要final修饰;而field是外部类MyApplication的字段,因而不需要final修饰。这种设计是基于什么理由呢?
我想这个问题应该从Java是如何实现匿名内部类的。其中有两点:
1、匿名内部类可以使用外部类的变量(局部或成员变来那个)。
2、匿名内部类中不同的方法可以共享这些变量。
根据这两点信息我们就可以分析,可能这些变量会在匿名内部类的字段中保存着,并且在构造的时候将他们的值/引用传入内部类。这样就可以保证同时实现上述两点了。
事实上,Java就是这样设计的,并且所谓匿名类,其实并不是匿名的,只是编译器帮我们命名了而已。这点我们可以通过这两个类编译出来的字节码看出来:
class levin.test.anonymous.MyApplication$1 implements levin.test.anonymous.Printer { final synthetic levin.test.anonymous.MyApplication this$0; private final synthetic long val$local2; private final synthetic java.lang.Integer val$param; MyApplication$1(levin.test.anonymous.MyApplication arg0, long arg1, java.lang.Integer arg2); 0 aload_0 [this] 1 aload_1 [arg0] 2 putfield levin.test.anonymous.MyApplication$1.this$0 : levin.test.anonymous.MyApplication [16] 5 aload_0 [this] 6 lload_2 [arg1] 7 putfield levin.test.anonymous.MyApplication$1.val$local2 : long [18] 10 aload_0 [this] 11 aload 4 [arg2] 13 putfield levin.test.anonymous.MyApplication$1.val$param : java.lang.Integer [20] 16 aload_0 [this] 17 invokespecial java.lang.Object() [22] 20 return Line numbers: [pc: 0, line: 1] [pc: 16, line: 13] Local variable table: [pc: 0, pc: 21] local: this index: 0 type: new levin.test.anonymous.MyApplication(){} public void print(); 0 getstatic java.lang.System.out : java.io.PrintStream [30] 3 ldc <String "Local value: 100"> [36] 5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38] 8 getstatic java.lang.System.out : java.io.PrintStream [30] 11 new java.lang.StringBuilder [44] 14 dup 15 ldc <String "Local2 value: "> [46] 17 invokespecial java.lang.StringBuilder(java.lang.String) [48] 20 aload_0 [this] 21 getfield levin.test.anonymous.MyApplication$1.val$local2 : long [18] 24 invokevirtual java.lang.StringBuilder.append(long) : java.lang.StringBuilder [50] 27 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54] 30 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38] 33 getstatic java.lang.System.out : java.io.PrintStream [30] 36 new java.lang.StringBuilder [44] 39 dup 40 ldc <String "Parameter: "> [58] 42 invokespecial java.lang.StringBuilder(java.lang.String) [48] 45 aload_0 [this] 46 getfield levin.test.anonymous.MyApplication$1.val$param : java.lang.Integer [20] 49 invokevirtual java.lang.StringBuilder.append(java.lang.Object) : java.lang.StringBuilder [60] 52 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54] 55 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38] 58 getstatic java.lang.System.out : java.io.PrintStream [30] 61 new java.lang.StringBuilder [44] 64 dup 65 ldc <String "Field value: "> [63] 67 invokespecial java.lang.StringBuilder(java.lang.String) [48] 70 aload_0 [this] 71 getfield levin.test.anonymous.MyApplication$1.this$0 : levin.test.anonymous.MyApplication [16] 74 invokestatic levin.test.anonymous.MyApplication.access$0(levin.test.anonymous.MyApplication) : int [65] 77 invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [71] 80 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54] 83 invokevirtual java.io.PrintStream.println(java.lang.String) : void [38] 86 return Line numbers: [pc: 0, line: 16] [pc: 8, line: 17] [pc: 33, line: 18] [pc: 58, line: 19] [pc: 86, line: 20] Local variable table: [pc: 0, pc: 87] local: this index: 0 type: new levin.test.anonymous.MyApplication(){} Inner classes: [inner class info: #1 levin/test/anonymous/MyApplication$1, outer class info: #0 inner name: #0, accessflags: 0 default] Enclosing Method: #66 #77 levin/test/anonymous/MyApplication.print(Ljava/lang/Integer;)V } |