乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      深入理解為什么Java中方法內(nèi)定義的內(nèi)部類可以訪問方法中的局部變量

       看風(fēng)景D人 2016-10-07

      開篇

      在我的上一篇博客 深入理解Java中為什么內(nèi)部類可以訪問外部類的成員  中, 通過使用javap工具反編譯內(nèi)部類的字節(jié)碼, 我們知道了為什么內(nèi)部類中可以訪問外部類的成員, 其實(shí)是編譯器在編譯內(nèi)部類的class文件時(shí),偷偷做了一些工作, 使內(nèi)部類持有外部類的引用, 并且通過在構(gòu)造方法上添加參數(shù)注入這個(gè)引用, 在調(diào)用構(gòu)造方法時(shí)默認(rèn)傳入了外部類的引用。 我們之所以感到疑惑, 就是因?yàn)榫幾g器使用的障眼法。當(dāng)我們把字節(jié)碼反編譯出來之后, 編譯器的這些小伎倆就會(huì)清清楚楚的展示在我們面前。 感興趣的朋友可以移步到上一篇博客, 博客鏈接: http://blog.csdn.NET/zhangjg_blog/article/details/20000769


      在本文中, 我們要對(duì)定義在方法中的內(nèi)部類進(jìn)行分析。 和上一篇博客一樣, 我們還是使用javap工具對(duì)內(nèi)部類的字節(jié)碼進(jìn)行解剖。 并且和上一篇文章進(jìn)行對(duì)比分析, 探究定義在外部類方法中的內(nèi)部類和定義在外部類中的內(nèi)部類有哪些相同之處和不同之處。 這篇博客的講解以上一篇為基礎(chǔ), 對(duì)這些知識(shí)點(diǎn)不是很熟悉的同學(xué), 強(qiáng)烈建議先讀上一篇博客。 博客的鏈接已經(jīng)在上面給出。



      定義在方法中的內(nèi)部類

      在平時(shí)寫代碼的過程中, 我們經(jīng)常會(huì)寫類似下面的代碼段:

      1. public class Test {  
      2.   
      3.     public static void main(String[] args) {  
      4.         final int count = 0;  
      5.           
      6.         new Thread(){  
      7.             public void run() {  
      8.                 int var = count;  
      9.             };  
      10.         }.start();  
      11.     }  
      12. }  

      這段代碼在main方法中定義了一個(gè)匿名內(nèi)部類, 并且創(chuàng)建了匿名內(nèi)部類的一個(gè)對(duì)象, 使用這個(gè)對(duì)象調(diào)用了匿名內(nèi)部類中的方法。 所有這些操作都在new Thread(){}.start() 這一句代碼中完成, 這不禁讓人感嘆java的表達(dá)能力還是很強(qiáng)的。 上面的代碼和以下代碼等價(jià):

      1. public class Test {  
      2.   
      3.     public static void main(String[] args) {  
      4.         final int count = 0;  
      5.           
      6.         //在方法中定義一個(gè)內(nèi)部類  
      7.         class MyThread extends Thread{  
      8.             public void run() {  
      9.                 int var = count;  
      10.             }  
      11.         }  
      12.           
      13.         new MyThread().start();  
      14.     }  
      15. }  

      這里我們不關(guān)心方法中匿名內(nèi)部類和非匿名內(nèi)部類的區(qū)別, 我們只需要知道, 這兩種方式都是定義在方法中的內(nèi)部類, 他們的工作原理是相同的。 在本文中主要根據(jù)非匿名內(nèi)部類講解。 


      讓我們仔細(xì)觀察上面的代碼都有哪些“奇怪”的行為:


      1 在外部類的main方法中有一個(gè)局部變量count, 并且在內(nèi)部類的run方法中訪問了這個(gè)count變量。 也就是說, 方法中定義的內(nèi)部類, 可以訪問方法中的局部變量(方法的參數(shù)也是局部變量);

      2 count變量使用final關(guān)鍵字修飾, 如果去掉final, 則編譯失敗。 也就是說被方法中的內(nèi)部類訪問的局部變量必須是final的。


      由于我們經(jīng)常這樣做, 這樣寫代碼, 久而久之養(yǎng)成了習(xí)慣, 就成了司空見慣的做法了。 但是如果要問為什么Java支持這樣的做法, 恐怕很少有人能說的出來。 在下面, 我們就會(huì)分析為什么Java支持這種做法, 讓我們不僅知其然, 還要知其所以然。



      為什么定義在方法中的內(nèi)部類可以訪問方法中的局部變量?


      1 當(dāng)被訪問的局部變量是編譯時(shí)可確定的字面常量時(shí)


      我們首先看這樣一段代碼, 本文的以下部分會(huì)以這樣的代碼進(jìn)行講解。 

      1. public class Outer {  
      2.   
      3.     void outerMethod(){  
      4.         final  String localVar = "abc";  
      5.           
      6.         /*定義在方法中的內(nèi)部類*/  
      7.         class Inner{  
      8.             void innerMethod(){  
      9.                 String a = localVar;  
      10.             }  
      11.         }  
      12.     }  
      13. }  

      在外部類的方法outerMethod中定義了成員變量 String localVar, 并且用一個(gè)編譯時(shí)字面量"abc"給他賦值。在 outerMethod方法中定義了內(nèi)部類Inner, 并且在內(nèi)部類的方法innerMethod中訪問了localVar變量。 接下來我們就根據(jù)這個(gè)例子來講解為什么可以這樣做。

      首先看編譯后的文件, 和普通的內(nèi)部類一樣, 定義在方法中的內(nèi)部類在編譯之后, 也有自己獨(dú)立的class文件:



      和普通內(nèi)部類的區(qū)別是, 普通內(nèi)部類的class文件名為Outer$Inner.class 。 而定義在方法中的內(nèi)部類的class文件名為Outer$<N>Inner.class 。 N代表數(shù)字, 如1, 2, 3 等 。 在外部類第一個(gè)方法中定義的內(nèi)部類, 編號(hào)為1, 同理在外部類第二個(gè)方法中定義的內(nèi)部類編號(hào)為2, 在外部類中第N個(gè)方法中定義的內(nèi)部類編號(hào)為N 。 這些都是題外話, 主要想說的是, 方法中的內(nèi)部類也有自己獨(dú)立的class文件。 

      我們通過javap反編譯工具, 把 Outer$1Inner.class 反編譯成可讀的形式。 關(guān)于javap工具的使用, 請(qǐng)參考我的上一篇博客。 反編譯的輸出結(jié)果如下:

      [plain] view plain copy
      在CODE上查看代碼片派生到我的代碼片
      1. Constant pool:  
      2.    #1 = Class              #2             //  Outer$1Inner  
      3.    #2 = Utf8               Outer$1Inner  
      4.    #3 = Class              #4             //  java/lang/Object  
      5.    #4 = Utf8               java/lang/Object  
      6.    #5 = Utf8               this$0  
      7.    #6 = Utf8               LOuter;  
      8.    #7 = Utf8               <init>  
      9.    #8 = Utf8               (LOuter;)V  
      10.    #9 = Utf8               Code  
      11.   #10 = Fieldref           #1.#11         //  Outer$1Inner.this$0:LOuter;  
      12.   #11 = NameAndType        #5:#6          //  this$0:LOuter;  
      13.   #12 = Methodref          #3.#13         //  java/lang/Object."<init>":()V  
      14.   #13 = NameAndType        #7:#14         //  "<init>":()V  
      15.   #14 = Utf8               ()V  
      16.   #15 = Utf8               LineNumberTable  
      17.   #16 = Utf8               LocalVariableTable  
      18.   #17 = Utf8               this  
      19.   #18 = Utf8               LOuter$1Inner;  
      20.   #19 = Utf8               innerMethod  
      21.   #20 = String             #21            //  abc  
      22.   #21 = Utf8               abc  
      23.   #22 = Utf8               a  
      24.   #23 = Utf8               Ljava/lang/String;  
      25.   #24 = Utf8               SourceFile  
      26.   #25 = Utf8               Outer.java  
      27.   #26 = Utf8               EnclosingMethod  
      28.   #27 = Class              #28            //  Outer  
      29.   #28 = Utf8               Outer  
      30.   #29 = NameAndType        #30:#14        //  outerMethod:()V  
      31.   #30 = Utf8               outerMethod  
      32.   #31 = Utf8               InnerClasses  
      33.   #32 = Utf8               Inner  
      34. {  
      35.   final Outer this$0;  
      36.     flags: ACC_FINAL, ACC_SYNTHETIC  
      37.   
      38.   Outer$1Inner(Outer);  
      39.     flags:  
      40.     Code:  
      41.       stack=2, locals=2, args_size=2  
      42.          0: aload_0  
      43.          1: aload_1  
      44.          2: putfield      #10                 // Field this$0:LOuter;  
      45.          5: aload_0  
      46.          6: invokespecial #12                 // Method java/lang/Object."<init>":()V  
      47.          9: return  
      48.       LineNumberTable:  
      49.         line 8: 0  
      50.       LocalVariableTable:  
      51.         Start  Length  Slot  Name   Signature  
      52.                0      10     0  this   LOuter$1Inner;  
      53.   
      54.   void innerMethod();  
      55.     flags:  
      56.     Code:  
      57.       stack=1, locals=2, args_size=1  
      58.          0: ldc           #20                 // String abc  
      59.          2: astore_1  
      60.          3: return  
      61.       LineNumberTable:  
      62.         line 10: 0  
      63.         line 11: 3  
      64.       LocalVariableTable:  
      65.         Start  Length  Slot  Name   Signature  
      66.                0       4     0  this   LOuter$1Inner;  
      67.                3       1     1     a   Ljava/lang/String;  
      68.   
      69. }  

      innerMethod方法中一共就以下有三個(gè)指令:
               0: ldc           #20                 // String abc
               2: astore_1
               3: return

      Idc指令的意思是將索引指向的常量池中的項(xiàng)壓入操作數(shù)棧。 這里的索引為20 , 引用的常量池中的項(xiàng)為字符串“abc” 。 這句話就揭示了內(nèi)部類訪問方法局部變量的原理。 讓我們從常量池第20項(xiàng)看起。 



      常量池中第20項(xiàng)確實(shí)是字符串“abc” 。 但是這個(gè)字符串“abc”明明是定義在外部類Outer中的, 因?yàn)槌霈F(xiàn)在外部類的outerMethod方法中。 為了查看這個(gè)“abc”是否在外部類中, 我們繼續(xù)反編譯外部類Outer.class 。 為了篇幅考慮, 在這里指給出Outer.class反編譯輸出的常量池的一部分。
      [plain] view plain copy
      在CODE上查看代碼片派生到我的代碼片
      1. ......  
      2. ......  
      3.   
      4. #13 = Utf8               LOuter;  
      5. #14 = Utf8               outerMethod  
      6. #15 = String             #16            //  abc  
      7. #16 = Utf8               abc  
      8.   
      9. ......  
      10. ......  

      我們可以看到, “abc”這個(gè)字符串確實(shí)出現(xiàn)在Outer.class常量池的第15項(xiàng)。 這就奇怪了, 明明是定義在外部類的字面量, 為什么會(huì)出現(xiàn)在 內(nèi)部類的常量池中呢? 其實(shí)這正是編譯器在編譯方法中定義的內(nèi)部類時(shí), 所做的額外工作。 

      下面我們將這個(gè)被內(nèi)部類訪問的局部變量改成整形的。 看看在字節(jié)碼層面上會(huì)有什么變化。 修改后的源碼如下:

      1. public class Outer {  
      2.   
      3.     void outerMethod(){  
      4.         final  int localVar = 1;  
      5.           
      6.         /*定義在方法中的內(nèi)部類*/  
      7.         class Inner{  
      8.             void innerMethod(){  
      9.                 int a = localVar;  
      10.             }  
      11.         }  
      12.     }  
      13. }  

      內(nèi)部類反編譯后的class文件如下: (由于在這里常量池不是重點(diǎn), 所以省略了常量池信息)

      1. {  
      2.   final Outer this$0;  
      3.     flags: ACC_FINAL, ACC_SYNTHETIC  
      4.   
      5.   Outer$1Inner(Outer);  
      6.     flags:  
      7.     Code:  
      8.       stack=2, locals=2, args_size=2  
      9.          0: aload_0  
      10.          1: aload_1  
      11.          2: putfield      #10                 // Field this$0:LOuter;  
      12.          5: aload_0  
      13.          6: invokespecial #12                 // Method java/lang/Object."<init>":()V  
      14.          9: return  
      15.       LineNumberTable:  
      16.         line 8: 0  
      17.       LocalVariableTable:  
      18.         Start  Length  Slot  Name   Signature  
      19.                0      10     0  this   LOuter$1Inner;  
      20.   
      21.   void innerMethod();  
      22.     flags:  
      23.     Code:  
      24.       stack=1, locals=2, args_size=1  
      25.          0: iconst_1  
      26.          1: istore_1  
      27.          2: return  
      28.       LineNumberTable:  
      29.         line 10: 0  
      30.         line 11: 2  
      31.       LocalVariableTable:  
      32.         Start  Length  Slot  Name   Signature  
      33.                0       3     0  this   LOuter$1Inner;  
      34.                2       1     1     a   I  
      35. }  

      從上面的輸出可以看到, innerMethod方法中的第一句字節(jié)碼為: 
      [plain] view plain copy
      在CODE上查看代碼片派生到我的代碼片
      1. iconst_1  

      這句字節(jié)碼的意義是:將int類型的常量 1 壓入操作數(shù)棧。 這就是在內(nèi)部類中訪問外部類方法中的局部變量int localVar = 1的原理。 由此可見, 當(dāng)內(nèi)部類中訪問的局部變量是int型的字面量時(shí), 編譯器直接將對(duì)該變量的訪問嵌入到內(nèi)部類的字節(jié)碼中, 也就是說, 在運(yùn)行時(shí), 方法中的內(nèi)部類和外部類, 和外部類方法中的局部變量就沒有任何關(guān)系了。 這也是編譯器所做的額外工作。

      上面兩種情況有一個(gè)共同點(diǎn), 那就是, 被內(nèi)部類訪問的外部了方法中的局部變量, 都是在編譯時(shí)可以確定的字面常量。 像下面這樣的形式都是編譯時(shí)可確定的字面常量:
      1. final  String localVar = "abc";  

      1. final  int localVar = 1;  

      他們之所以被稱為字面常量, 是因?yàn)樗麄儽籪inal修飾, 運(yùn)行時(shí)不可改變, 當(dāng)編譯器在編譯源文件時(shí), 可以確定他們的值, 也可以確定他們?cè)谶\(yùn)行時(shí)不會(huì)被修改, 所以可以實(shí)現(xiàn)類似C語(yǔ)言宏替換的功能。也就是說雖然在編寫源代碼時(shí), 在另一個(gè)類中訪問的是當(dāng)前類定義的這個(gè)變量, 但是在編譯成字節(jié)碼時(shí), 卻把這個(gè)變量的值放入了訪問這個(gè)變量的另一個(gè)類的常量池中, 或直接將這個(gè)變量的值嵌入另一個(gè)類的字節(jié)碼指令中。 運(yùn)行時(shí)這兩個(gè)類各不相干, 各自訪問各自的常量池, 各自執(zhí)行各自的字節(jié)碼指令。在編譯方法中定義的內(nèi)部類時(shí), 編譯器的行為就是這樣的。 


      2 當(dāng)被訪問的局部變量的值在編譯時(shí)不可確定時(shí)


      那么當(dāng)方法中定義的內(nèi)部類訪問的局部變量不是編譯時(shí)可確定的字面常量, 又會(huì)怎么樣呢?想要讓這個(gè)局部變量變成編譯時(shí)不可確定的, 只需要將源碼修改如下:

      1. public class Outer {  
      2.   
      3.     void outerMethod(){  
      4.         final  String localVar = getString();  
      5.           
      6.         /*定義在方法中的內(nèi)部類*/  
      7.         class Inner{  
      8.             void innerMethod(){  
      9.                 String a = localVar;  
      10.             }  
      11.         }  
      12.           
      13.         new Inner();  
      14.     }  
      15.   
      16.     String getString(){  
      17.         return "aa";  
      18.     }  
      19. }  

      由于使用getString方法的返回值為localVar賦值, 所以在編譯時(shí)期, 編譯器不可確定localVar的值, 必須在運(yùn)行時(shí)執(zhí)行了getString方法之后才能確定它的值。 既然編譯時(shí)不不可確定, 那么像上面那樣的處理就行不通了。 那么在這種情況下, 內(nèi)部類是通過什么機(jī)制訪問方法中的局部變量的呢? 讓我們繼續(xù)反編譯內(nèi)部類的字節(jié)碼:

      [plain] view plain copy
      在CODE上查看代碼片派生到我的代碼片
      1. Constant pool:  
      2.    #1 = Class              #2             //  Outer$1Inner  
      3.    #2 = Utf8               Outer$1Inner  
      4.    #3 = Class              #4             //  java/lang/Object  
      5.    #4 = Utf8               java/lang/Object  
      6.    #5 = Utf8               this$0  
      7.    #6 = Utf8               LOuter;  
      8.    #7 = Utf8               val$localVar  
      9.    #8 = Utf8               Ljava/lang/String;  
      10.    #9 = Utf8               <init>  
      11.   #10 = Utf8               (LOuter;Ljava/lang/String;)V  
      12.   #11 = Utf8               Code  
      13.   #12 = Fieldref           #1.#13         //  Outer$1Inner.this$0:LOuter;  
      14.   #13 = NameAndType        #5:#6          //  this$0:LOuter;  
      15.   #14 = Fieldref           #1.#15         //  Outer$1Inner.val$localVar:Ljava/la  
      16. ng/String;  
      17.   #15 = NameAndType        #7:#8          //  val$localVar:Ljava/lang/String;  
      18.   #16 = Methodref          #3.#17         //  java/lang/Object."<init>":()V  
      19.   #17 = NameAndType        #9:#18         //  "<init>":()V  
      20.   #18 = Utf8               ()V  
      21.   #19 = Utf8               LineNumberTable  
      22.   #20 = Utf8               LocalVariableTable  
      23.   #21 = Utf8               this  
      24.   #22 = Utf8               LOuter$1Inner;  
      25.   #23 = Utf8               innerMethod  
      26.   #24 = Utf8               a  
      27.   #25 = Utf8               SourceFile  
      28.   #26 = Utf8               Outer.java  
      29.   #27 = Utf8               EnclosingMethod  
      30.   #28 = Class              #29            //  Outer  
      31.   #29 = Utf8               Outer  
      32.   #30 = NameAndType        #31:#18        //  outerMethod:()V  
      33.   #31 = Utf8               outerMethod  
      34.   #32 = Utf8               InnerClasses  
      35.   #33 = Utf8               Inner  
      36. {  
      37.   final Outer this$0;  
      38.     flags: ACC_FINAL, ACC_SYNTHETIC  
      39.   
      40.   Outer$1Inner(Outer, java.lang.String);  
      41.     flags:  
      42.     Code:  
      43.       stack=2, locals=3, args_size=3  
      44.          0: aload_0  
      45.          1: aload_1  
      46.          2: putfield      #12                 // Field this$0:LOuter;  
      47.          5: aload_0  
      48.          6: aload_2  
      49.          7: putfield      #14                 // Field val$localVar:Ljava/lang/String;  
      50.         10: aload_0  
      51.         11: invokespecial #16                 // Method java/lang/Object."<init>":()V  
      52.         14: return  
      53.       LineNumberTable:  
      54.         line 8: 0  
      55.       LocalVariableTable:  
      56.         Start  Length  Slot  Name   Signature  
      57.                0      15     0  this   LOuter$1Inner;  
      58.   
      59.   void innerMethod();  
      60.     flags:  
      61.     Code:  
      62.       stack=1, locals=2, args_size=1  
      63.          0: aload_0  
      64.          1: getfield      #14                 // Field val$localVar:Ljava/lang/String;  
      65.          4: astore_1  
      66.          5: return  
      67.       LineNumberTable:  
      68.         line 10: 0  
      69.         line 11: 5  
      70.       LocalVariableTable:  
      71.         Start  Length  Slot  Name   Signature  
      72.                0       6     0  this   LOuter$1Inner;  
      73.                5       1     1     a   Ljava/lang/String;  
      74. }  

      首先來看它的構(gòu)造方法。 方法的簽名為:
      [plain] view plain copy
      在CODE上查看代碼片派生到我的代碼片
      1. Outer$1Inner(Outer, java.lang.String);  

      我們只到, 如果不定義構(gòu)造方法, 那么編譯器會(huì)為這個(gè)類自動(dòng)生成一個(gè)無參數(shù)的構(gòu)造方法。 這個(gè)說法在這里就行不通了, 因?yàn)槲覀兛吹剑?這個(gè)內(nèi)部類的構(gòu)造方法又兩個(gè)參數(shù)。 至于第一個(gè)參數(shù), 是指向外部類對(duì)象的引用, 在前面一篇博客中已經(jīng)詳細(xì)的介紹過了, 不明白的可以先看上一篇博客, 這里就不再重復(fù)敘述。這也說明了方法中的內(nèi)部類和類中定義的內(nèi)部類有相同的地方, 既然他們都是內(nèi)部類, 就都持有指向外部類對(duì)象的引用。  我們來分析第二個(gè)參數(shù), 他是String類型的, 和在內(nèi)部類中訪問的局部變量localVar的類型相同。 再看構(gòu)造方法中編號(hào)為6和7的字節(jié)碼指令:
      [plain] view plain copy
      在CODE上查看代碼片派生到我的代碼片
      1. 6: aload_2  
      2. 7: putfield      #14                 // Field val$localVar:Ljava/lang/String;  

      這句話的意思是, 使用構(gòu)造方法的第二個(gè)參數(shù), 為當(dāng)前這個(gè)內(nèi)部類對(duì)象的成員變量賦值, 這個(gè)被賦值的成員變量的名字是 val$localVar 。 由此可見, 編譯器自動(dòng)為內(nèi)部類增加了一個(gè)成員變量, 其實(shí)這個(gè)成員變量就是被訪問的外部類方法中的局部變量。 這個(gè)局部變量在創(chuàng)建內(nèi)部類對(duì)象時(shí), 通過構(gòu)造方法注入。 在調(diào)用構(gòu)造方法時(shí), 編譯器會(huì)默認(rèn)為這個(gè)參數(shù)傳入外部類方法中的局部變量的值。 

      再看內(nèi)部類中的方法innerMethod中是如何訪問這個(gè)所謂的“局部變量的”。 看innerMethod中的前兩條字節(jié)碼:
      [plain] view plain copy
      在CODE上查看代碼片派生到我的代碼片
      1. 0: aload_0  
      2. 1: getfield      #14                 // Field val$localVar:Ljava/lang/String;  

      這兩條指令的意思是, 訪問成員變量val$localVar的值。 而源代碼中是訪問外部類方法中局部變量的值。 所以, 在這里將編譯時(shí)對(duì)外部類方法中的局部變量的訪問, 轉(zhuǎn)化成運(yùn)行時(shí)對(duì)當(dāng)前內(nèi)部類對(duì)象中成員變量的訪問。 

      在源代碼層面上, 它的工作方式有點(diǎn)像這樣: (注意, 下面的代碼不符合Java的語(yǔ)法, 只是模擬編譯器的行為)

      1. public class Outer {  
      2.   
      3.     void outerMethod(){  
      4.         final  String localVar = getString();  
      5.           
      6.         /*定義在方法中的內(nèi)部類*/  
      7.         class Inner{  
      8.             /*下面兩個(gè)成員變量都是編譯器自動(dòng)加上的*/  
      9.             final Outer this$0; //指向外部類對(duì)象的引用  
      10.             final String val$localVar; //被訪問的外部類方法中的局部變量的值  
      11.               
      12.             /*構(gòu)造方法, 兩個(gè)參數(shù)都是編譯器添加的*/  
      13.             public Inner(Outer outer, String outerMethodLocal){  
      14.                 this.this$0 = outer;  
      15.                 this.val$localVar = outerMethodLocal;  
      16.                 super();  
      17.             }  
      18.               
      19.             void innerMethod(){  
      20.                   
      21.                 /*將對(duì)外部類方法中的變量的訪問, 轉(zhuǎn)換成對(duì)當(dāng)前對(duì)象的成員變量的訪問*/  
      22.                 //String a = localVar;  
      23.                 String a = val$localVar;   
      24.             }  
      25.         }  
      26.           
      27.         /*在外部類方法中創(chuàng)建內(nèi)部類對(duì)象時(shí), 傳入相應(yīng)的參數(shù),  
      28.             這兩個(gè)參數(shù)分別是當(dāng)前外部類的引用, 和當(dāng)前方法中的局部變量*/  
      29.         //new Inner();  
      30.         new Inner(this, localVar);  
      31.           
      32.     }  
      33.   
      34.     String getString(){  
      35.         return "aa";  
      36.     }  
      37. }  

      講到這里, 內(nèi)部類的行為就比較清晰了。 總結(jié)一下就是: 當(dāng)方法中定義的內(nèi)部類訪問的方法局部變量的值, 不是在編譯時(shí)能確定的字面常量時(shí), 編譯器會(huì)為內(nèi)部類增加一個(gè)成員變量, 在運(yùn)行時(shí), 將對(duì)外部類方法中局部變量的訪問。 轉(zhuǎn)換成對(duì)這個(gè)內(nèi)部類成員變量的方法。 這就要求內(nèi)部類中的這個(gè)新增的成員變量和外部類方法中的局部變量具有相同的值。 編譯器通過為內(nèi)部類的構(gòu)造方法增加參數(shù), 并在調(diào)用構(gòu)造器初始化內(nèi)部類對(duì)象時(shí)傳入這個(gè)參數(shù), 來初始化內(nèi)部類中的這個(gè)成員變量的值。 所以, 雖然在源文件中看起來是訪問的外部類方法的局部變量, 其實(shí)運(yùn)行時(shí)訪問的是內(nèi)部類對(duì)象自己的成員變量。 



      為什么被方法內(nèi)的內(nèi)部類訪問的局部變量必須是final的

      上面我們講解了, 方法中的內(nèi)部類訪問方法局部變量是怎么實(shí)現(xiàn)的。 那么為什么這個(gè)局部變量必須是final的呢? 我認(rèn)為有以下兩個(gè)原因:

      1 當(dāng)局部變量的值為編譯時(shí)可確定的字面常量時(shí)( 如字符串“abc”或整數(shù)1 ), 通過final修飾, 可以實(shí)現(xiàn)類似C語(yǔ)言的編譯時(shí)宏替換功能。 這樣的話, 外部類和內(nèi)部類各自訪問自己的常量池, 各自執(zhí)行各自的字節(jié)碼指令, 看起來就像共同訪問外部類方法中的局部變量。 這樣就可以達(dá)到語(yǔ)義上的一致性。 由于存在內(nèi)部類和外部類中的常量值是一樣的, 并且是不可改變的,這樣就可以達(dá)到數(shù)值訪問的一致性。

      2 當(dāng)局部變量的值不是可在編譯時(shí)確定的字面常量時(shí)(比如通過方法調(diào)用為它賦值), 這種情況下, 編譯器給內(nèi)部類增加相同類型的成員變量, 并通過構(gòu)造函數(shù)將外部類方法中的局部變量的值賦給這個(gè)新增的內(nèi)部類成員變量。

       如果這個(gè)局部變量是基本數(shù)據(jù)類型時(shí), 直接拷貝數(shù)值給內(nèi)部類成員變量。代碼示例和運(yùn)行時(shí)內(nèi)存布局是這樣的:

      1. public class Outer {  
      2.   
      3.     void outerMethod(){  
      4.         final  int localVar = getInt();  
      5.           
      6.         /*定義在方法中的內(nèi)部類*/  
      7.         class Inner{  
      8.             void innerMethod(){  
      9.                 int a = localVar;  
      10.             }  
      11.         }  
      12.   
      13.         new Inner();  
      14.     }  
      15.       
      16.     int getInt(){ return 1; }  
      17. }  


      這樣的話, 內(nèi)部類和外部類各自訪問自己的基本數(shù)據(jù)類型的變量, 他們的變量值一樣, 并且不可修改, 這樣就保證了語(yǔ)義上和數(shù)值訪問上的一致性。

      如果這個(gè)局部變量是引用數(shù)據(jù)類型時(shí), 拷貝外部類方法中的引用值給內(nèi)部類對(duì)象的成員變量, 這樣的話, 他們就指向了同一個(gè)對(duì)象。 代碼示例和運(yùn)行時(shí)的內(nèi)存布局如下:

      1. public class Outer {  
      2.   
      3.     void outerMethod(){  
      4.         final  Person localVar = getPerson();  
      5.           
      6.         /*定義在方法中的內(nèi)部類*/  
      7.         class Inner{  
      8.             void innerMethod(){  
      9.                 Person a = localVar;  
      10.             }  
      11.         }  
      12.   
      13.         new Inner();  
      14.     }  
      15.       
      16.     Person getPerson(){ return new Person("zhangjg", 30); }  
      17. }  


      由于這兩個(gè)引用變量指向同一個(gè)對(duì)象, 所以通過引用訪問的對(duì)象的數(shù)據(jù)是一樣的, 由于他們都不能再指向其他對(duì)象(被final修飾), 所以可以保證內(nèi)部類和外部類數(shù)據(jù)訪問的一致性



        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多