Java中”失效”的private修飾符解析
在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 OuterClass Compiled 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: return public 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: return static java.lang.String access$0(OuterClass); Code: 0: aload_0 1: getfield #15; //Field language:Ljava/lang/String; 4: areturn static 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: areturn static 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\$InnerClass Compiled 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: return public 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\$InnerClass Compiled 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: return static 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 AnotherOuterClass Compiled 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: return public 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
- Java中的native修飾符Java
- #Java教程:訪問修飾符:public、protected、預設、private @FDDLCJava
- Java 修飾符Java 修飾符
- java修飾符Java
- java中final修飾符的用法Java
- Java的static修飾符Java
- 面試題 private protected public default修飾符的 作用域面試題
- Java 常用修飾符Java
- 關於Java中各種修飾符與訪問修飾符的說明 (轉)Java
- Java的訪問修飾符Java
- 深入理解 Java 中 protected 修飾符Java
- java static修飾符的問題Java
- Java的“友好的”訪問指示符(修飾符)Java
- java修飾符使用指南Java
- 如何獲取java類中的欄位修飾符?Java
- JAVA java學習(24)——————java修飾符Java
- java中public,protected,private關鍵字以及預設訪問許可權修飾符的用法細節Java訪問許可權
- Java中final修飾符都有什麼作用Java
- Vue - 按鍵修飾符 && 系統修飾符Vue
- java oop 修飾符&關鍵字JavaOOP
- Java 修飾符順序問題Java 修飾符
- Java修飾符關鍵詞大全Java
- Java修飾符關鍵字的順序Java
- Java之private關鍵字修飾成員變數Java變數
- java常用修飾符(隨堂筆記)Java筆記
- vue 事件修飾符Vue事件
- vue sync 修飾符Vue
- 繼承&修飾符繼承
- iOS __block修飾符iOSBloC
- java基礎學習之四:修飾符Java
- Vue的.sync修飾符的使用Vue
- iOS中assign和weak修飾符的區別iOS
- 09-02 Java語言基礎(修飾符)Java
- Vue事件修飾符詳解Vue事件
- TypeScript 類訪問修飾符TypeScript
- C語言中的各種修飾符C語言
- vue中v-model和.sync修飾符區別Vue