Java中的訪問控制修飾符已經困惑筆者多時,其中較復雜的情況一直不能理解透徹。今天下定決心,系統、全面地研究Java中的訪問控制修飾符的所有方面,並整理成這篇文章,希望有同樣疑惑的讀者讀完后能有所收獲。如果文章中出現錯誤,歡迎評論指出,共同交流~
說在前面:這篇文章只研究Java中訪問控制修飾符聲明類的變量/方法的情況。
先拋出結論:
* 成員變量/方法的訪問權限
* private default protected public
* 自己包自己類 √ √ √ √
* 自己包別的類 √ √ √
* 別的包別的類有繼承關系② ① √
* 別的包別的類無繼承關系 √
①:子類可以繼承,但是不能訪問父類的成員變量/方法(一般來說,可以訪問就可以繼承)。
②:有繼承關系說明訪問對象所在的類是父類。
1. 讓我們來看一下Java中訪問控制修飾符的定義。
Java中,可以使用訪問控制符來保護對類、變量、方法和構造方法的訪問。
訪問的形式有以下四種:
· 某個類的成員變量訪問某個類的成員變量
· 某個類的成員變量訪問某個類的成員方法
· 某個類的成員方法訪問某個類的成員變量
· 某個類的成員方法訪問某個類的成員方法
ps:以下代碼均以第三種形式為例,其他形式基本一致。
根據訪問對象的不同,訪問的方式又可划分為兩大類:
· 訪問對象在同一個類,此時可以通過[成員變量/方法的名字]直接訪問。
class A { int a = 10; void printA() { System.out.println(a); } }
printA()要訪問a,因為它們在同一個類,所以可以通過a直接訪問。
· 訪問對象在不同類(假設訪問對象在類B),此時可以通過聲明、初始化B的一個對象,通過[對象名.成員變量/方法的名字]進行訪問。
ps:這種情況僅限於成員方法訪問成員變量/方法。
class A { void printB() { B ob = new B(); System.out.println(ob.b); } } class B { int b = 10; }
A中的printB()要訪問B中的b,因為它們不在同一個類,所以可以在printB()中聲明、初始化B的一個對象ob,通過ob.b進行訪問。
此外,當訪問對象為靜態變量/方法時,可以通過[訪問對象所在類的類名.成員變量/方法的名字]進行訪問。
class A { static int a = 10; int doubleA = A.a * 2; void printB() { System.out.println(B.b); } } class B { static int b = 10; }
doubleA要訪問a,由於a為靜態變量,因此可以通過A.a進行訪問。
A中的printB()要訪問B中的b,由於b為靜態變量,因此可以通過B.b進行訪問。
2.結論中提到了包,我們來看一下Java中包的定義和作用。
為了更好地組織類,Java提供了包機制,用於區別類名的命名空間。
包的作用
- 1 把功能相似或相關的類或接口組織在同一個包中,方便類的查找和使用。
- 2 如同文件夾一樣,包也采用了樹形目錄的存儲方式。同一個包中的類名字是不同的,不同的包中的類的名字是可以相同的,當同時調用兩個不同包中相同類名的類時,應該加上包名加以區別。因此,包可以避免名字沖突。
- 3 包也限定了訪問權限,擁有包訪問權限的類才能訪問某個包中的類。
Java使用包(package)這種機制是為了防止命名沖突,訪問控制,提供搜索和定位類(class)、接口、枚舉(enumerations)和注釋(annotation)等。
關於包的使用方法,請參考Java教程 包(package),在此不詳細贅述。
值得注意的是,import關鍵字引入的是class文件,而非java文件。
3.結論中還提到了繼承,我們來看一下Java中繼承的定義。
繼承是java面向對象編程技術的一塊基石,因為它允許創建分等級層次的類。繼承可以理解為一個對象從另一個對象獲取屬性的過程。
關於繼承的細節,請參照Java教程 繼承,在此不詳細贅述。
需要理解的是,子類繼承父類的成員變量/方法時,是先訪問再繼承。因此上面訪問權限的規則同樣適用於繼承。
在同一個包里,如果父類的某個成員變量/方法可以被訪問,則該成員變量/方法可以被繼承。即如果在子類成員方法中,聲明、初始化父類的一個對象后,可以通過[對象名.成員變量/方法a]訪問a,則聲明、初始化子類的一個對象后,也一定可以通過[對象名.成員變量/方法a]訪問a。
class A extends B { void printB() { B ob = new B(); System.out.println(ob.b); A ob2 = new A(); System.out.println(ob2.b); } } class B { int b = 10; }
A繼承B,因此A繼承B的成員變量b。由於A在printB()中,聲明、初始化B的一個對象ob后,可以通過ob.b訪問b,則聲明、初始化A的一個對象ob2后,可能通過ob2.b訪問b。(可以訪問則可以繼承)。
然而,在不同包里,子類繼承父類時,子類只能訪問父類的public型成員變量/方法,卻能繼承父類的protected和public型成員變量/方法。(請看下面的例子)
值得注意的是,子類繼承父類的成員變量/方法,並不意味着這些成員變量/方法存在於子類,因此不能通過[成員變量/方法的名字]直接訪問。可以理解為繼承而來的成員變量/方法進入了子類的異次元(霧)。
當然,如果繼承而來的成員變量/方法被重寫,這些成員變量/方法就存在於子類了,此時可以通過[成員變量/方法的名字]直接訪問。
此處不討論多態的情況,請參照Java教程 多態。
回到結論,讓我們來一層層地驗證Java中的訪問控制修飾符。
/* Stark.java */ package winter.is.coming; public class Stark { private boolean ned; boolean robb; protected boolean sansa; public boolean arya; void howIsNed() { System.out.println(ned); } } class Snow { void whoseBastard() { Stark stark = new Stark(); // System.out.println(stark.ned); 不可訪問 System.out.println(stark.robb); } } /* Greyjoy.java */ import winter.is.coming.Stark; public class Greyjoy extends Stark { void betray() { Stark stark = new Stark(); // System.out.println(stark.robb); 不可訪問 // System.out.println(stark.sansa); 不可訪問 Greyjoy greyjoy = new Greyjoy(); // System.out.println(greyjoy.robb); 不可訪問 System.out.println(greyjoy.sansa); } } /* Bolton.java */ import winter.is.coming.Stark; public class Bolton { void flay() { Stark stark = new Stark(); System.out.println(stark.arya); } }
① 自己包自己類 -- private可訪問
Stark中的howIsNed()可以訪問Stark中private型的ned。
② 自己包別的類 -- default可訪問
Snow中的whoseBastard()可以訪問Stark中default型的robb,不可以訪問Stark中private型的ned。
③ 別的包別的類有繼承關系 -- protected可繼承,不可訪問
Greyjoy中的betray()可以繼承Stark中protected型的sansa,不可以訪問Stark中protected型的sansa,也不可以繼承和訪問Stark中default型的robb。
④ 別的包別的類無繼承關系 -- public可訪問
Bolton中的flay()可以訪問Stark中public型的arya。