<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0

    昨天有一個比較愛思考的同事和我提起一個問題:為什么匿名內部類使用的局部變量和參數需要final修飾,而外部類的成員變量則不用?對這個問題我一直作為默認的語法了,木有仔細想過為什么(在分析完后有點印象在哪本書上看到過,但是就是沒有找到,難道是我的幻覺?呵呵)。雖然沒有想過,但是還是借著之前研究過字節碼的基礎上,分析了一些,感覺上是找到了一些答案,分享一下;也希望有大牛給指出一些不足的地方。

    假如我們有以下的代碼:

     1 interface Printer {
     2     public void print();
     3 }
     4 
     5 class MyApplication {
     6     private int field = 10;
     7     
     8     public void print(final Integer param) {
     9         final long local = 100;
    10         final long local2 = param.longValue() + 100;
    11         Printer printer = new Printer() {
    12             @Override
    13             public void print() {
    14                 System.out.println("Local value: " + local);
    15                 System.out.println("Local2 value: " + local2);
    16                 System.out.println("Parameter: " + param);
    17                 System.out.println("Field value: " + field);
    18             }
    19         };
    20         printer.print();
    21     }
    22 }

    這里因為param要在匿名內部類的print()方法中使用,因而它要用final修飾;local/local2是局部變量,因而也需要final修飾;而field是外部類MyApplication的字段,因而不需要final修飾。這種設計是基于什么理由呢?

     

    我想這個問題應該從Java是如何實現匿名內部類的。其中有兩點:
    1.
    匿名內部類可以使用外部類的變量(局部或成員變來那個)

    2. 匿名內部類中不同的方法可以共享這些變量

    根據這兩點信息我們就可以分析,可能這些變量會在匿名內部類的字段中保存著,并且在構造的時候將他們的值/引用傳入內部類。這樣就可以保證同時實現上述兩點了。

     

    事實上,Java就是這樣設計的,并且所謂匿名類,其實并不是匿名的,只是編譯器幫我們命名了而已。這點我們可以通過這兩個類編譯出來的字節碼看出來:

     1 // Compiled from Printer.java (version 1.6 : 50.0, super bit)
     2 class levin.test.anonymous.MyApplication$1 implements levin.test.anonymous.Printer {
     3   
     4   // Field descriptor #8 Llevin/test/anonymous/MyApplication;
     5   final synthetic levin.test.anonymous.MyApplication this$0;
     6   
     7   // Field descriptor #10 J
     8   private final synthetic long val$local2;
     9   
    10   // Field descriptor #12 Ljava/lang/Integer;
    11   private final synthetic java.lang.Integer val$param;
    12   
    13   // Method descriptor #14 (Llevin/test/anonymous/MyApplication;JLjava/lang/Integer;)V
    14   // Stack: 3, Locals: 5
    15   MyApplication$1(levin.test.anonymous.MyApplication arg0, long arg1, java.lang.Integer arg2);
    16      0  aload_0 [this]
    17      1  aload_1 [arg0]
    18      2  putfield levin.test.anonymous.MyApplication$1.this$0 : levin.test.anonymous.MyApplication [16]
    19      5  aload_0 [this]
    20      6  lload_2 [arg1]
    21      7  putfield levin.test.anonymous.MyApplication$1.val$local2 : long [18]
    22     10  aload_0 [this]
    23     11  aload 4 [arg2]
    24     13  putfield levin.test.anonymous.MyApplication$1.val$param : java.lang.Integer [20]
    25     16  aload_0 [this]
    26     17  invokespecial java.lang.Object() [22]
    27     20  return
    28       Line numbers:
    29         [pc: 0, line: 1]
    30         [pc: 16, line: 13]
    31       Local variable table:
    32         [pc: 0, pc: 21] local: this index: 0 type: new levin.test.anonymous.MyApplication(){}
    33   
    34   // Method descriptor #24 ()V
    35   // Stack: 4, Locals: 1
    36   public void print();
    37      0  getstatic java.lang.System.out : java.io.PrintStream [30]
    38      3  ldc <String "Local value: 100"> [36]
    39      5  invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
    40      8  getstatic java.lang.System.out : java.io.PrintStream [30]
    41     11  new java.lang.StringBuilder [44]
    42     14  dup
    43     15  ldc <String "Local2 value: "> [46]
    44     17  invokespecial java.lang.StringBuilder(java.lang.String) [48]
    45     20  aload_0 [this]
    46     21  getfield levin.test.anonymous.MyApplication$1.val$local2 : long [18]
    47     24  invokevirtual java.lang.StringBuilder.append(long) : java.lang.StringBuilder [50]
    48     27  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54]
    49     30  invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
    50     33  getstatic java.lang.System.out : java.io.PrintStream [30]
    51     36  new java.lang.StringBuilder [44]
    52     39  dup
    53     40  ldc <String "Parameter: "> [58]
    54     42  invokespecial java.lang.StringBuilder(java.lang.String) [48]
    55     45  aload_0 [this]
    56     46  getfield levin.test.anonymous.MyApplication$1.val$param : java.lang.Integer [20]
    57     49  invokevirtual java.lang.StringBuilder.append(java.lang.Object) : java.lang.StringBuilder [60]
    58     52  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54]
    59     55  invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
    60     58  getstatic java.lang.System.out : java.io.PrintStream [30]
    61     61  new java.lang.StringBuilder [44]
    62     64  dup
    63     65  ldc <String "Field value: "> [63]
    64     67  invokespecial java.lang.StringBuilder(java.lang.String) [48]
    65     70  aload_0 [this]
    66     71  getfield levin.test.anonymous.MyApplication$1.this$0 : levin.test.anonymous.MyApplication [16]
    67     74  invokestatic levin.test.anonymous.MyApplication.access$0(levin.test.anonymous.MyApplication) : int [65]
    68     77  invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [71]
    69     80  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [54]
    70     83  invokevirtual java.io.PrintStream.println(java.lang.String) : void [38]
    71     86  return
    72       Line numbers:
    73         [pc: 0, line: 16]
    74         [pc: 8, line: 17]
    75         [pc: 33, line: 18]
    76         [pc: 58, line: 19]
    77         [pc: 86, line: 20]
    78       Local variable table:
    79         [pc: 0, pc: 87] local: this index: 0 type: new levin.test.anonymous.MyApplication(){}
    80 
    81   Inner classes:
    82     [inner class info: #1 levin/test/anonymous/MyApplication$1, outer class info: #0
    83      inner name: #0, accessflags: 0 default]
    84   Enclosing Method: #66  #77 levin/test/anonymous/MyApplication.print(Ljava/lang/Integer;)V
    85 }

     1 // Compiled from Printer.java (version 1.6 : 50.0, super bit)
     2 class levin.test.anonymous.MyApplication {
     3   
     4   // Field descriptor #6 I
     5   private int field;
     6   
     7   // Method descriptor #8 ()V
     8   // Stack: 2, Locals: 1
     9   MyApplication();
    10      0  aload_0 [this]
    11      1  invokespecial java.lang.Object() [10]
    12      4  aload_0 [this]
    13      5  bipush 10
    14      7  putfield levin.test.anonymous.MyApplication.field : int [12]
    15     10  return
    16       Line numbers:
    17         [pc: 0, line: 7]
    18         [pc: 4, line: 8]
    19         [pc: 10, line: 7]
    20       Local variable table:
    21         [pc: 0, pc: 11] local: this index: 0 type: levin.test.anonymous.MyApplication
    22   
    23   // Method descriptor #19 (Ljava/lang/Integer;)V
    24   // Stack: 6, Locals: 7
    25   public void print(java.lang.Integer param);
    26      0  ldc2_w <Long 100> [20]
    27      3  lstore_2 [local]
    28      4  aload_1 [param]
    29      5  invokevirtual java.lang.Integer.longValue() : long [22]
    30      8  ldc2_w <Long 100> [20]
    31     11  ladd
    32     12  lstore 4 [local2]
    33     14  new levin.test.anonymous.MyApplication$1 [28]
    34     17  dup
    35     18  aload_0 [this]
    36     19  lload 4 [local2]
    37     21  aload_1 [param]
    38     22  invokespecial levin.test.anonymous.MyApplication$1(levin.test.anonymous.MyApplication, long, java.lang.Integer) [30]
    39     25  astore 6 [printer]
    40     27  aload 6 [printer]
    41     29  invokeinterface levin.test.anonymous.Printer.print() : void [33] [nargs: 1]
    42     34  return
    43       Line numbers:
    44         [pc: 0, line: 11]
    45         [pc: 4, line: 12]
    46         [pc: 14, line: 13]
    47         [pc: 27, line: 22]
    48         [pc: 34, line: 23]
    49       Local variable table:
    50         [pc: 0, pc: 35] local: this index: 0 type: levin.test.anonymous.MyApplication
    51         [pc: 0, pc: 35] local: param index: 1 type: java.lang.Integer
    52         [pc: 4, pc: 35] local: local index: 2 type: long
    53         [pc: 14, pc: 35] local: local2 index: 4 type: long
    54         [pc: 27, pc: 35] local: printer index: 6 type: levin.test.anonymous.Printer
    55   
    56   // Method descriptor #45 (Llevin/test/anonymous/MyApplication;)I
    57   // Stack: 1, Locals: 1
    58   static synthetic int access$0(levin.test.anonymous.MyApplication arg0);
    59     0  aload_0 [arg0]
    60     1  getfield levin.test.anonymous.MyApplication.field : int [12]
    61     4  ireturn
    62       Line numbers:
    63         [pc: 0, line: 8]
    64 
    65   Inner classes:
    66     [inner class info: #28 levin/test/anonymous/MyApplication$1, outer class info: #0
    67      inner name: #0, accessflags: 0 default]
    68 }

    從這兩段字節碼中可以看出,編譯器為我們的匿名類起了一個叫MyApplication$1的名字,它包含了三個final字段(這里synthetic修飾符是指這些字段是由編譯器生成的,它們并不存在于源代碼中):

    MyApplication的應用this$0

    longval$local2

    Integer引用val$param

    這些字段在構造函數中賦值,而構造函數則是在MyApplication.print()方法中調用。

    由此,我們可以得出一個結論:Java對匿名內部類的實現是通過編譯器來支持的,即通過編譯器幫我們產生一個匿名類的類名,將所有在匿名類中用到的局部變量和參數做為內部類的final字段,同是內部類還會引用外部類的實例。其實這里少了local的變量,這是因為local是編譯器常量,編譯器對它做了替換的優化。

    其實Java中很多語法都是通過編譯器來支持的,而在虛擬機/字節碼上并沒有什么區別,比如這里的final關鍵字,其實細心的人會發現在字節碼中,param參數并沒有final修飾,而final本身的很多實現就是由編譯器支持的。類似的還有Java中得泛型和逆變、協變等。這是題外話。

     

    有了這個基礎后,我們就可以來分析為什么有些要用final修飾,有些卻不用的問題。

    首先我們來分析local2變量,在匿名類中,它是通過構造函數傳入到匿名類字段中的,因為它是基本類型,因而在夠著函數中賦值時(撇開對函數參數傳遞不同虛擬機的不同實現而產生的不同效果),它事實上只是值的拷貝;因而加入我們可以在匿名類中得print()方法中對它賦值,那么這個賦值對外部類中得local2變量不會有影響,而程序員在讀代碼中,是從上往下讀的,所以很容易誤認為這段代碼賦值會對外部類中得local2變量本身產生影響,何況在源碼中他們的名字都是一樣的,所以我認為了避免這種confuse導致的一些問題,Java設計者才設計出了這樣的語法。

    對引用類型,其實也是一樣的,因為引用的傳遞事實上也只是傳遞引用的數值(簡單的可以理解成為地址),因而對param,如果可以在匿名類中賦值,也不會在外部類的print()后續方法產生影響。雖然這樣,我們還是可以在內部類中改變引用內部的值的,如果引用類型不是只讀類型的話;在這里Integer是只讀類型,因而我們沒法這樣做。(如果學過C++的童鞋可以想想常量指針和指針常量的區別)。

     

    現在還剩下最后一個問題:為什么引用外部類的字段卻是可以不用final修飾的呢?細心的童鞋可能也已經發現答案了,因為內部類保存了外部類的引用,因而內部類中對任何字段的修改都回真實的反應到外部類實例本身上,所以不需要用final來修飾它。

     

    這個問題基本上就分析到這里了,不知道我有沒有表達清楚了。

    加點題外話吧。

    首先是,對這里的字節碼,其實還有一點可以借鑒的地方,就是內部類在使用外部類的字段時不是直接取值,而是通過編譯器在外部類中生成的靜態的access$0()方法來取值,我的理解,這里Java設計者想盡量避免其他類直接訪問一個類的數據成員,同時生成的access$0()方法還可以被其他類所使用,這遵循了面向對象設計中的兩個重要原則:封裝和復用。

     

    另外,對這個問題也讓我意識到了即使是語言語法層面上的設計都是有原因可循的,我們要善于多問一些為什么,理解這些設計的原因和局限,記得曾聽到過一句話:知道一門技術的局限,我們才能很好的理解這門技術可以用來做什么。也只有這樣我們才能不斷的提高自己。在解決了這個問題后,我突然冒出了一句說Java這樣設計也是合理的。是啊,語法其實就一幫人創建的一種解決某些問題的方案,當然有合理和不合理之分,我們其實不用對它視若神圣。

     

    之前有進過某著名高校的研究生群,即使在那里,碼農論也是甚囂塵上,其實碼農不碼農并不是因為程序員這個職位引起的,而是個人引起的,我們要不斷理解代碼內部的本質才能避免一直做碼農的命運那。個人愚見而已,呵呵。



     

     

    posted on 2011-11-23 01:49 DLevin 閱讀(9510) 評論(8)  編輯  收藏 所屬分類: Core Java

    FeedBack:
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?[未登錄]
    2011-11-23 10:08 | tester
    主要就是一句話: 字段和局部變量的生命周期不一樣。
    樓主的帖子我沒太看明白,但下面這個我看明白了。
    請參考:
    http://stackoverflow.com/questions/5801829/why-a-non-final-local-variable-cannot-be-used-inside-an-inner-class-and-inste  回復  更多評論
      
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?
    2011-11-23 10:30 | 何楊
    碼農都想擺脫,我身邊的人都在學習外語好當PM以擺脫碼農。  回復  更多評論
      
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?
    2011-11-23 11:27 | 瘋狂
    分析的不錯,但有些地方不是很明確,實際上是為了保持內外一致。設置成final是為了防止外部重新賦值。也就是和copy有關系,既然是copy,就要內外一致。針對外部類的字段卻是可以不用final修飾,這個因為內部類最終只有this引用,其實和傳遞引用局部變量是一個機制。  回復  更多評論
      
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?
    2011-11-23 13:07 | DLevin
    我感覺應該不是生命周期引起的,Java里的生命周期是由虛擬機管理的,所以局部變量和外部類實例難說那個生命周期更長,在你給的鏈接中,這段話到是一個蠻好的解釋:So Java could copy the value of the variable w/o bothering with this compiler error, but it instead forces you to declare the variable as final to tell you "hey, remember it gets copied into the inner class, so if you could change it afterwards, then you get a severe inconsistency. So you can't change it, and we're clear with that." @tester
      回復  更多評論
      
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?
    2011-11-23 13:08 | DLevin
    恩,我貌似表述的有點繁瑣了,你這個解釋更加簡潔明了一些~~~~@瘋狂
      回復  更多評論
      
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?[未登錄]
    2012-05-15 17:03 | 張君
    差不多勒 我再消化消化   回復  更多評論
      
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?
    2014-12-11 15:17 | g897
    為什么引用外部類的字段卻是可以不用final修飾的呢?細心的童鞋可能也已經發現答案了,因為內部類保存了外部類的引用,因而內部類中對任何字段的修改都回真實的反應到外部類實例本身上,所以不需要用final來修飾它。
    ///////////////////////////////////////////////////////////////////////
    難道不是因為全局變量不會被自動回收,所以不會出現局部內部類調用該成員變量時該變量已經被自動回收的情況,才不用定義final嗎?

    新手,請指教  回復  更多評論
      
    # re: [多問幾個為什么]為什么匿名內部類中引用的局部變量和參數需要final而成員字段不用?
    2014-12-12 19:08 | DLevin
    @g897
    final能控制回收流程?你記錯成finalize了?Java哪來的全局變量,又哪來的不會自動回收的說法?  回復  更多評論
      
    主站蜘蛛池模板: 亚洲av无码片在线播放| 亚洲国产精华液2020| 成人免费毛片内射美女APP| 美女被羞羞网站免费下载| 久久亚洲精品成人777大小说| 国产91免费在线观看| 最新亚洲人成无码网站| 亚洲天堂久久精品| 国产又大又粗又硬又长免费 | 久久永久免费人妻精品| 亚洲国产熟亚洲女视频| 亚洲熟妇av一区二区三区漫画| 精品成在人线AV无码免费看 | 最近中文字幕mv免费高清在线 | 亚洲1234区乱码| 在线日韩日本国产亚洲| 丁香花在线观看免费观看| 中文字幕在线免费视频| 亚洲国产精品自在自线观看 | 亚欧乱色国产精品免费视频| 亚洲男人天堂2022| 亚洲成人在线网站| 亚洲国产精品一区二区九九 | 日本xxxx色视频在线观看免费| 久久水蜜桃亚洲AV无码精品| 久久精品亚洲精品国产色婷 | 亚洲精品成人无限看| 日韩免费电影在线观看| 在线观看免费视频资源| 久久成人永久免费播放| 亚洲va中文字幕| 亚洲一区精品视频在线| 亚洲精品视频在线| 亚洲色偷拍另类无码专区| 成人国产mv免费视频| 一二三四影视在线看片免费 | 男男gay做爽爽免费视频| 精品亚洲国产成人| 亚洲美女一区二区三区| 亚洲AV永久青草无码精品| 国产亚洲精品免费视频播放 |