<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ù)  更多評論
      
    主站蜘蛛池模板: 国产成人精品日本亚洲| 中文字幕精品亚洲无线码二区| 亚洲精品不卡视频| 免费成人在线视频观看| 亚洲阿v天堂在线| 青青青国产手机频在线免费观看| 亚洲中文字幕在线第六区| 久久九九免费高清视频| 无套内射无矿码免费看黄| 免费va在线观看| 亚洲一卡二卡三卡四卡无卡麻豆| 色片在线免费观看| 亚洲熟妇久久精品| 国产一级大片免费看| 一区二区三区AV高清免费波多| MM131亚洲国产美女久久| 暖暖日本免费中文字幕| 亚洲成a人片在线观看中文!!!| 免费可以看黄的视频s色| jizzjizz亚洲日本少妇| 猫咪免费人成网站在线观看| 国产99在线|亚洲| 国产成人涩涩涩视频在线观看免费 | 99久久99久久精品免费看蜜桃| 国产亚洲精品xxx| 4虎1515hh永久免费| 亚洲宅男精品一区在线观看| 国产精品免费小视频| 成人免费av一区二区三区| 亚洲精品免费视频| 免费看的黄色大片| 国产精品1024在线永久免费| 日本免费人成黄页网观看视频| 特黄特色的大片观看免费视频| 亚洲国产成人一区二区三区 | 中文字幕天天躁日日躁狠狠躁免费| 亚洲伊人久久大香线蕉在观| 无码专区AAAAAA免费视频| 亚洲精品123区在线观看| 亚洲成人一区二区| 无码精品一区二区三区免费视频|