Java類訪問許可權
1 類訪問許可權
1.1 四種訪問許可權解析
Java
有四種訪問許可權, 其中三種有訪問許可權修飾符,分別為private,public和protected
,還有一種不帶任何修飾符
四種訪問許可權:
private
:Java
語言中對訪問許可權限制的最窄的修飾符,一般稱之為私有的
。被其修飾的類、屬性以及方法只能被該類的物件訪問,其子類不能訪問,更不能允許跨包訪問。default
:即不加任何訪問修飾符,通常稱為預設訪問模式
。該模式下,只允許在同一個包中進行訪問。protect
保護形
。被其修飾的類、屬性以及方法只能被類本身的方法及子類訪問,即使子類在不同的包中也可以訪問。public
:Java
語言中訪問限制最寬的修飾符,一般稱之為公共的
。被其修飾的類、屬性以及方法不僅可以跨類訪問,而且允許跨包(package
)訪問。
下面用表格的形式來展示四種訪問許可權之間的異同點,這樣會更加形象。表格如下所示:
同一個類 | 同一個包 | 不同包的子類 | 不同包的非子類 | |
---|---|---|---|---|
Private | √ | |||
Default | √ | √ | ||
Protected | √ | √ | √ | |
Public | √ | √ | √ | √ |
1.2 Protected分析
假設在包accesscontrol
下面有AccessControlDemo
和 Base
兩個類,其中protected double price;
是Base類
的成員變數,因為兩個類在同一個包中,所以在AccessControlDemo
類中可以直接訪問System.out.println(base.price);
具體例項如下:
accesscontrol.AccessControlDemo
package accesscontrol; public class AccessControlDemo { public static void main(String[] args) { // TODO Auto-generated method stub Base base=new Base("123-1",120.1); System.out.println(base.price); } }
accesscontrol.Base
package accesscontrol;
public class Base {
private String isbn;
protected double price;
//預設建構函式
public Base() {}
//建構函式,如果只定義帶引數的建構函式而不定義預設建構函式,那麼Base的子類必須定義顯式建構函式
public Base(String isbn, double price) {
this.isbn = isbn;
this.price = price;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
但是假如我們將AccessControlDemo
這個類移到test
這個包中,我們會發現eclipse
中提示錯誤,編譯無法通過,因為在test
包中對protected
型別的成員變數不可見。
假如我們在test
包中建立一個Base
類的子類Bulk
,也就是說Bulk
是Base
類不同包的子類。那麼在Bulk
類中能夠直接訪問protected double price;
這個基層自Base
類的成員變數,例項如下:
test.AccessControlDemo
package test;
public class AccessControlDemo {
public static void main(String[] args) {
Bulk bulk=new Bulk("123-1",120.1);
bulk.print();
}
}
test.Bulk
package test;
import accesscontrol.Base;
public class Bulk extends Base {
public Bulk() {
super();
}
public Bulk(String isbn, double price) {
super(isbn, price);
}
public void print()
{
System.out.println(this.price);
}
}
1.3 private失效情況
在Java
程式設計中,使用private
關鍵字修飾了某個成員,只有這個成員所在的類和這個類的方法可以使用,其他的類都無法訪問到這個private
成員。
1.3.1 Java內部類
在Java
中相信很多人都用過內部類,Java
允許在一個類裡面定義另一個類,類裡面的類就是內部類,也叫做巢狀類。一個簡單的內部類實現可以如下
class OuterClass {
class InnerClass{
}
}
一個我們在程式設計中經常用到的場景,就是在一個內部類裡面訪問外部類的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
的反編譯結果
$ 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
的反編譯結果
$ 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
表面上看上去失效了,但實際上是沒有的,而是在呼叫時通過間接的方法來獲取私有的屬性。