日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
內(nèi)部類引用局部變量與外部類成員變量的問(wèn)題思考

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

假如我們有以下的代碼:

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

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

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

1、匿名內(nèi)部類可以使用外部類的變量(局部或成員變來(lá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)我們可以通過(guò)這兩個(gè)類編譯出來(lái)的字節(jié)碼看出來(lái):

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

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

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

long值val$local2

Integer引用val$param

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

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

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

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

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

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

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

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

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

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

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

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


當(dāng)前名稱:內(nèi)部類引用局部變量與外部類成員變量的問(wèn)題思考
當(dāng)前地址:http://m.5511xx.com/article/cdseoip.html