<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

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

    假如我們有以下的代碼:

     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 }

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

     

    我想這個問題應(yīng)該從Java是如何實(shí)現(xiàn)匿名內(nèi)部類的。其中有兩點(diǎn):
    1.
    匿名內(nèi)部類可以使用外部類的變量(局部或成員變來那個)

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

    根據(jù)這兩點(diǎn)信息我們就可以分析,可能這些變量會在匿名內(nèi)部類的字段中保存著,并且在構(gòu)造的時候?qū)⑺麄兊闹?/span>/引用傳入內(nèi)部類。這樣就可以保證同時實(shí)現(xiàn)上述兩點(diǎn)了。

     

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

     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 }

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

    MyApplication的應(yīng)用this$0

    longval$local2

    Integer引用val$param

    這些字段在構(gòu)造函數(shù)中賦值,而構(gòu)造函數(shù)則是在MyApplication.print()方法中調(diào)用。

    由此,我們可以得出一個結(jié)論:Java對匿名內(nèi)部類的實(shí)現(xiàn)是通過編譯器來支持的,即通過編譯器幫我們產(chǎn)生一個匿名類的類名,將所有在匿名類中用到的局部變量和參數(shù)做為內(nèi)部類的final字段,同是內(nèi)部類還會引用外部類的實(shí)例。其實(shí)這里少了local的變量,這是因?yàn)?/span>local是編譯器常量,編譯器對它做了替換的優(yōu)化。

    其實(shí)Java中很多語法都是通過編譯器來支持的,而在虛擬機(jī)/字節(jié)碼上并沒有什么區(qū)別,比如這里的final關(guān)鍵字,其實(shí)細(xì)心的人會發(fā)現(xiàn)在字節(jié)碼中,param參數(shù)并沒有final修飾,而final本身的很多實(shí)現(xiàn)就是由編譯器支持的。類似的還有Java中得泛型和逆變、協(xié)變等。這是題外話。

     

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

    首先我們來分析local2變量,在匿名類中,它是通過構(gòu)造函數(shù)傳入到匿名類字段中的,因?yàn)樗腔绢愋停蚨趬蛑瘮?shù)中賦值時(撇開對函數(shù)參數(shù)傳遞不同虛擬機(jī)的不同實(shí)現(xiàn)而產(chǎn)生的不同效果),它事實(shí)上只是值的拷貝;因而加入我們可以在匿名類中得print()方法中對它賦值,那么這個賦值對外部類中得local2變量不會有影響,而程序員在讀代碼中,是從上往下讀的,所以很容易誤認(rèn)為這段代碼賦值會對外部類中得local2變量本身產(chǎn)生影響,何況在源碼中他們的名字都是一樣的,所以我認(rèn)為了避免這種confuse導(dǎo)致的一些問題,Java設(shè)計者才設(shè)計出了這樣的語法。

    對引用類型,其實(shí)也是一樣的,因?yàn)橐玫膫鬟f事實(shí)上也只是傳遞引用的數(shù)值(簡單的可以理解成為地址),因而對param,如果可以在匿名類中賦值,也不會在外部類的print()后續(xù)方法產(chǎn)生影響。雖然這樣,我們還是可以在內(nèi)部類中改變引用內(nèi)部的值的,如果引用類型不是只讀類型的話;在這里Integer是只讀類型,因而我們沒法這樣做。(如果學(xué)過C++的童鞋可以想想常量指針和指針常量的區(qū)別)。

     

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

     

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

    加點(diǎn)題外話吧。

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

     

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

     

    之前有進(jìn)過某著名高校的研究生群,即使在那里,碼農(nóng)論也是甚囂塵上,其實(shí)碼農(nóng)不碼農(nóng)并不是因?yàn)槌绦騿T這個職位引起的,而是個人引起的,我們要不斷理解代碼內(nèi)部的本質(zhì)才能避免一直做碼農(nóng)的命運(yùn)那。個人愚見而已,呵呵。



     

     

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

    FeedBack:
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要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  回復(fù)  更多評論
      
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要final而成員字段不用?
    2011-11-23 10:30 | 何楊
    碼農(nóng)都想擺脫,我身邊的人都在學(xué)習(xí)外語好當(dāng)PM以擺脫碼農(nóng)。  回復(fù)  更多評論
      
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要final而成員字段不用?
    2011-11-23 11:27 | 瘋狂
    分析的不錯,但有些地方不是很明確,實(shí)際上是為了保持內(nèi)外一致。設(shè)置成final是為了防止外部重新賦值。也就是和copy有關(guān)系,既然是copy,就要內(nèi)外一致。針對外部類的字段卻是可以不用final修飾,這個因?yàn)閮?nèi)部類最終只有this引用,其實(shí)和傳遞引用局部變量是一個機(jī)制。  回復(fù)  更多評論
      
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要final而成員字段不用?
    2011-11-23 13:07 | DLevin
    我感覺應(yīng)該不是生命周期引起的,Java里的生命周期是由虛擬機(jī)管理的,所以局部變量和外部類實(shí)例難說那個生命周期更長,在你給的鏈接中,這段話到是一個蠻好的解釋: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
      回復(fù)  更多評論
      
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要final而成員字段不用?
    2011-11-23 13:08 | DLevin
    恩,我貌似表述的有點(diǎn)繁瑣了,你這個解釋更加簡潔明了一些~~~~@瘋狂
      回復(fù)  更多評論
      
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要final而成員字段不用?[未登錄]
    2012-05-15 17:03 | 張君
    差不多勒 我再消化消化   回復(fù)  更多評論
      
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要final而成員字段不用?
    2014-12-11 15:17 | g897
    為什么引用外部類的字段卻是可以不用final修飾的呢?細(xì)心的童鞋可能也已經(jīng)發(fā)現(xiàn)答案了,因?yàn)閮?nèi)部類保存了外部類的引用,因而內(nèi)部類中對任何字段的修改都回真實(shí)的反應(yīng)到外部類實(shí)例本身上,所以不需要用final來修飾它。
    ///////////////////////////////////////////////////////////////////////
    難道不是因?yàn)槿肿兞坎粫蛔詣踊厥眨圆粫霈F(xiàn)局部內(nèi)部類調(diào)用該成員變量時該變量已經(jīng)被自動回收的情況,才不用定義final嗎?

    新手,請指教  回復(fù)  更多評論
      
    # re: [多問幾個為什么]為什么匿名內(nèi)部類中引用的局部變量和參數(shù)需要final而成員字段不用?
    2014-12-12 19:08 | DLevin
    @g897
    final能控制回收流程?你記錯成finalize了?Java哪來的全局變量,又哪來的不會自動回收的說法?  回復(fù)  更多評論
      
    主站蜘蛛池模板: 亚洲av日韩aⅴ无码色老头| 久久精品亚洲综合| 亚洲va乱码一区二区三区| 暖暖日本免费中文字幕| 亚洲va无码专区国产乱码| 久久免费观看国产精品| 亚洲乱亚洲乱淫久久| 无码AV片在线观看免费| 亚洲特级aaaaaa毛片| 搡女人免费视频大全| 在线视频亚洲一区| 免费大片黄手机在线观看| 亚洲国产AV无码一区二区三区| 性xxxxx免费视频播放| 亚洲国色天香视频| 国产在线观看麻豆91精品免费| 在线观看日本免费a∨视频| 亚洲国产高清在线精品一区| 可以免费看黄的网站| 国产亚洲精品成人AA片| 免费日本黄色网址| 两个人看的www免费视频中文| 51视频精品全部免费最新| 亚洲乱码在线播放| 国产成人免费a在线视频色戒| 亚洲成AV人片一区二区| 99精品视频在线观看免费专区 | 亚洲成人免费网址| 亚洲乱人伦精品图片| 四虎影视大全免费入口| jizz免费在线影视观看网站| 亚洲人成网站影音先锋播放| 免费看AV毛片一区二区三区| 老司机精品免费视频| 亚洲一级免费视频| 无码国产亚洲日韩国精品视频一区二区三区 | 久久一区二区三区免费| 亚洲视频免费在线看| 国产一级淫片a视频免费观看| 亚洲国产综合91精品麻豆| 无码视频免费一区二三区|