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

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

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

    qileilove

    blog已經(jīng)轉(zhuǎn)移至github,大家請?jiān)L問 http://qaseven.github.io/

    內(nèi)部類引用局部變量與外部類成員變量的問題思考

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

      假如我們有以下的代碼:

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

      我想這個(gè)問題應(yīng)該從Java是如何實(shí)現(xiàn)匿名內(nèi)部類的。其中有兩點(diǎn):

      1、匿名內(nèi)部類可以使用外部類的變量(局部或成員變來那個(gè))。

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

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

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

  • // 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 
  • }
  •  

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

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

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

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

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

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

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

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

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

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

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

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

     

     

    posted on 2011-11-28 12:00 順其自然EVO 閱讀(167) 評(píng)論(0)  編輯  收藏


    只有注冊用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    <2011年11月>
    303112345
    6789101112
    13141516171819
    20212223242526
    27282930123
    45678910

    導(dǎo)航

    統(tǒng)計(jì)

    常用鏈接

    留言簿(55)

    隨筆分類

    隨筆檔案

    文章分類

    文章檔案

    搜索

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 亚洲妇女熟BBW| 日本媚薬痉挛在线观看免费| 粉色视频成年免费人15次| 久久久久久亚洲Av无码精品专口| 亚洲精品国产va在线观看蜜芽| 波多野结衣在线免费视频| 三年片在线观看免费西瓜视频| 国产亚洲美女精品久久久久| 亚洲精品**中文毛片| 亚洲乱码精品久久久久..| 又粗又黄又猛又爽大片免费| 两个人的视频高清在线观看免费 | 91av视频免费在线观看| caoporn国产精品免费| 黑人粗长大战亚洲女2021国产精品成人免费视频 | 亚洲成色WWW久久网站| 亚洲国产成人五月综合网| 日韩特黄特色大片免费视频| 国产大片线上免费观看| 最近中文字幕完整版免费高清| 香蕉视频在线免费看| 污网站免费在线观看| 亚洲国产成人久久精品大牛影视| 亚洲成av人片不卡无码| 亚洲人成网址在线观看 | 8x8x华人永久免费视频| 久久久久国产免费| 国内少妇偷人精品视频免费| 鲁丝片一区二区三区免费| 黄色网站软件app在线观看免费| 久久免费国产精品| 三级黄色片免费看| XXX2高清在线观看免费视频| 不卡视频免费在线观看| 中文字幕久精品免费视频| 久久国产精品成人免费| 久久午夜夜伦鲁鲁片免费无码| 久久99精品视免费看| 18禁黄网站禁片免费观看不卡| jjizz全部免费看片| 毛片免费观看的视频|