内部类引用局部变量与外部类成员变量的问题思考

发表于:2011-11-28 09:21

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:上善若水    来源:51Testing软件测试网采编

  昨天有一个比较爱思考的同事和我提起一个问题:为什么匿名内部类使用的局部变量和参数需要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就是这样设计的,并且所谓匿名类,其实并不是匿名的,只是编译器帮我们命名了而已。这点我们可以通过这两个类编译出来的字节码看出来:

  • // Compiled from Printer.java (version 1.6 : 50.0, super bit) 
  • class levin.test.anonymous.MyApplication$1 implements levin.test.anonymous.Printer { 
  •    
  •   // Field descriptor #8 Llevin/test/anonymous/MyApplication; 
  •   final synthetic levin.test.anonymous.MyApplication this$0
  •    
  •   // Field descriptor #10 J 
  •   private final synthetic long val$local2; 
  •    
  •   // Field descriptor #12 Ljava/lang/Integer; 
  •   private final synthetic java.lang.Integer val$param; 
  •    
  •   // Method descriptor #14 (Llevin/test/anonymous/MyApplication;JLjava/lang/Integer;)V 
  •   // Stack: 3, Locals: 5 
  •   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(){} 
  •    
  •   // Method descriptor #24 ()V 
  •   // Stack: 4, Locals: 1 
  •   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 
  • }
  • 31/3123>
    《2023软件测试行业现状调查报告》独家发布~

    关注51Testing

    联系我们

    快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

    法律顾问:上海兰迪律师事务所 项棋律师
    版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
    投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

    沪ICP备05003035号

    沪公网安备 31010102002173号