Java:"失效"的private修飾符_java

來源:互聯網
上載者:User

在Java編程中,使用private關鍵字修飾了某個成員,只有這個成員所在的類和這個類的方法可以使用,其他的類都無法訪問到這個private成員。

上面描述了private修飾符的基本職能,今天來研究一下private功能失效的情況。

Java內部類

在Java中相信很多人都用過內部類,Java允許在一個類裡面定義另一個類,類裡面的類就是內部類,也叫做嵌套類。一個簡單的內部類實現可以如下

class OuterClass {  class InnerClass{  }}

今天的問題和Java內部類相關,只涉及到部分和本文研究相關的內部類知識,具體關於Java內部類後續的文章會介紹。

第一次失效?

一個我們在編程中經常用到的情境,就是在一個內部類裡面訪問外部類的private成員變數或者方法,這是可以的。如下面的代碼實現。

public class OuterClass { private String language = "en"; private String region = "US";   public class InnerClass {   public void printOuterClassPrivateFields() {     String fields = "language=" + language + ";region=" + region;     System.out.println(fields);   } }  public static void main(String[] args) {   OuterClass outer = new OuterClass();   OuterClass.InnerClass inner = outer.new InnerClass();   inner.printOuterClassPrivateFields(); }}

這是為什麼呢,不是private修飾的成員只能被成員所述的類才能訪問嗎?難道private真的失效了嗎?

編譯器在搗鬼?

我們使用javap命令查看一下產生的兩個class檔案

OuterClass的反編譯結果

15:30 $ javap -c OuterClassCompiled from "OuterClass.java"public class OuterClass extends java.lang.Object{public OuterClass(); Code:  0: aload_0  1: invokespecial  #11; //Method java/lang/Object."<init>":()V  4: aload_0  5: ldc #13; //String en  7: putfield #15; //Field language:Ljava/lang/String;  10: aload_0  11: ldc #17; //String US  13: putfield #19; //Field region:Ljava/lang/String;  16: returnpublic static void main(java.lang.String[]); Code:  0: new #1; //class OuterClass  3: dup  4: invokespecial  #27; //Method "<init>":()V  7: astore_1  8: new #28; //class OuterClass$InnerClass  11: dup  12: aload_1  13: dup  14: invokevirtual  #30; //Method java/lang/Object.getClass:()Ljava/lang/Class;  17: pop  18: invokespecial  #34; //Method OuterClass$InnerClass."<init>":(LOuterClass;)V  21: astore_2  22: aload_2  23: invokevirtual  #37; //Method OuterClass$InnerClass.printOuterClassPrivateFields:()V  26: returnstatic java.lang.String access$0(OuterClass); Code:  0: aload_0  1: getfield #15; //Field language:Ljava/lang/String;  4: areturnstatic java.lang.String access$1(OuterClass); Code:  0: aload_0  1: getfield #19; //Field region:Ljava/lang/String;  4: areturn}

咦?不對,在OuterClass中我們並沒有定義這兩個方法

static java.lang.String access$0(OuterClass); Code:  0: aload_0  1: getfield #15; //Field language:Ljava/lang/String;  4: areturnstatic java.lang.String access$1(OuterClass); Code:  0: aload_0  1: getfield #19; //Field region:Ljava/lang/String;  4: areturn}

從給出來的注釋來看,access$0返回outerClass的language屬性;access$1返回outerClass的region屬性。並且這兩個方法都接受OuterClass的執行個體作為參數。這兩個方法為什麼產生呢,有什麼作用呢?我們看一下內部類的反編譯結果就知道了。

OuterClass$InnerClass的反編譯結果

15:37 $ javap -c OuterClass\$InnerClassCompiled from "OuterClass.java"public class OuterClass$InnerClass extends java.lang.Object{final OuterClass this$0;public OuterClass$InnerClass(OuterClass); Code:  0: aload_0  1: aload_1  2: putfield #10; //Field this$0:LOuterClass;  5: aload_0  6: invokespecial  #12; //Method java/lang/Object."<init>":()V  9: returnpublic void printOuterClassPrivateFields(); Code:  0: new #20; //class java/lang/StringBuilder  3: dup  4: ldc #22; //String language=  6: invokespecial  #24; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V  9: aload_0  10: getfield #10; //Field this$0:LOuterClass;  13: invokestatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;  16: invokevirtual  #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  19: ldc #37; //String ;region=  21: invokevirtual  #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  24: aload_0  25: getfield #10; //Field this$0:LOuterClass;  28: invokestatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;  31: invokevirtual  #33; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  34: invokevirtual  #42; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;  37: astore_1  38: getstatic  #46; //Field java/lang/System.out:Ljava/io/PrintStream;  41: aload_1  42: invokevirtual  #52; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  45: return}

下面代碼調用access$0的代碼,其目的是得到OuterClass的language 私人屬性。

13:   invokestatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;

下面代碼調用了access$1的代碼,其目的是得到OutherClass的region 私人屬性。

28:   invokestatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;

注意:在內部類構造的時候,會將外部類的引用傳遞進來,並且作為內部類的一個屬性,所以內部類會持有一個其外部類的引用。

this$0就是內部類持有的外部類引用,通過構造方法傳遞引用並賦值。

final OuterClass this$0;public OuterClass$InnerClass(OuterClass); Code:  0: aload_0  1: aload_1  2: putfield #10; //Field this$0:LOuterClass;  5: aload_0  6: invokespecial  #12; //Method java/lang/Object."<init>":()V  9: return

小結

這部分private看上去失效可,實際上並沒有失效,因為當內部類調用外部類的私人屬性時,其真正的執行是調用了編譯器產生的屬性的靜態方法(即acess$0,access$1等)來擷取這些屬性值。這一切都是編譯器的特殊處理。

這次也失效?

如果說上面的寫法很常用,那麼這樣的寫法是不是很少接觸,但是卻可以運行。

public class AnotherOuterClass { public static void main(String[] args) {   InnerClass inner = new AnotherOuterClass().new InnerClass();   System.out.println("InnerClass Filed = " + inner.x); } class InnerClass {   private int x = 10; }}

和上面一樣,使用javap反編譯看一下。不過這次我們先看一下InnerClass的結果

16:03 $ javap -c AnotherOuterClass\$InnerClassCompiled from "AnotherOuterClass.java"class AnotherOuterClass$InnerClass extends java.lang.Object{final AnotherOuterClass this$0;AnotherOuterClass$InnerClass(AnotherOuterClass); Code:  0: aload_0  1: aload_1  2: putfield #12; //Field this$0:LAnotherOuterClass;  5: aload_0  6: invokespecial  #14; //Method java/lang/Object."<init>":()V  9: aload_0  10: bipush  10  12: putfield #17; //Field x:I  15: returnstatic int access$0(AnotherOuterClass$InnerClass); Code:  0: aload_0  1: getfield #17; //Field x:I  4: ireturn}

又出現了,編譯器又自動產生了一個擷取私人屬性的後門方法access$0一次來擷取x的值。

AnotherOuterClass.class的反編譯結果

16:08 $ javap -c AnotherOuterClassCompiled from "AnotherOuterClass.java"public class AnotherOuterClass extends java.lang.Object{public AnotherOuterClass(); Code:  0: aload_0  1: invokespecial  #8; //Method java/lang/Object."<init>":()V  4: returnpublic static void main(java.lang.String[]); Code:  0: new #16; //class AnotherOuterClass$InnerClass  3: dup  4: new #1; //class AnotherOuterClass  7: dup  8: invokespecial  #18; //Method "<init>":()V  11: dup  12: invokevirtual  #19; //Method java/lang/Object.getClass:()Ljava/lang/Class;  15: pop  16: invokespecial  #23; //Method AnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V  19: astore_1  20: getstatic  #26; //Field java/lang/System.out:Ljava/io/PrintStream;  23: new #32; //class java/lang/StringBuilder  26: dup  27: ldc #34; //String InnerClass Filed =  29: invokespecial  #36; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V  32: aload_1  33: invokestatic #39; //Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I  36: invokevirtual  #43; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;  39: invokevirtual  #47; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;  42: invokevirtual  #51; //Method java/io/PrintStream.println:(Ljava/lang/String;)V  45: return}

其中這句調用就是外部類通過內部類的執行個體擷取私人屬性x的操作

33:   invokestatic #39; //Method AnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I

再來個總結

其中java官方文檔 有這樣一句話

if the member or constructor is declared private, then access is permitted if and only if it occurs within the body of the top level class (§7.6) that encloses the declaration of the member or constructor.

意思是 如果(內部類的)成員和構造方法設定成了私人修飾符,若且唯若其外部類訪問時是允許的。

如何讓內部類私人成員不被外部存取

相信看完上面兩部分,你會覺得,內部類的私人成員想不被外部類訪問都很困難吧,誰讓編譯器“愛管閑事”呢,其實也是可以做到的。那就是使用匿名內部類。

由於mRunnable對象的類型為Runnable,而不是匿名內部類的類型(我們無法正常拿到),而Runanble中沒有x這個屬性,所以mRunnable.x是不被允許的。

public class PrivateToOuter { Runnable mRunnable = new Runnable(){   private int x=10;   @Override   public void run() {     System.out.println(x);   } }; public static void main(String[] args){   PrivateToOuter p = new PrivateToOuter();   //System.out.println("anonymous class private filed= "+ p.mRunnable.x); //not allowed   p.mRunnable.run(); // allowed }}

最後總結

在本文中,private表面上看上去失效了,但實際上是沒有的,而是在調用時通過間接的方法來擷取私人的屬性。

Java的內部類構造時持有對外部類的應用,C++不會,這一點和C++不一樣。

深入Java細節的書籍

Java編程思想
Sun 公司核心技術叢書:Effective Java中文版
深入理解Java虛擬機器:JVM進階特性與最佳實務

以上就是對Java private 修飾符的資料整理,後續繼續補充相關資料,謝謝大家對本站的支援!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.