# 5.2 Java訪問指示符
針對類內每個成員的每個定義,Java訪問指示符`public`,`protected`以及`private`都置于它們的最前面——無論它們是一個數據成員,還是一個方法。每個訪問指示符都只控制著對那個特定定義的訪問。這與C++存在著顯著不同。在C++中,訪問指示符控制著它后面的所有定義,直到又一個訪問指示符加入為止。
通過千絲萬縷的聯系,程序為所有東西都指定了某種形式的訪問。在后面的小節里,大家要學習與各類訪問有關的所有知識。首次從默認訪問開始。
## 5.2.1 “友好的”
如果根本不指定訪問指示符,就象本章之前的所有例子那樣,這時會出現什么情況呢?默認的訪問沒有關鍵字,但它通常稱為“友好”(Friendly)訪問。這意味著當前包內的其他所有類都能訪問“友好的”成員,但對包外的所有類來說,這些成員卻是“私有”(Private)的,外界不得訪問。由于一個編譯單元(一個文件)只能從屬于單個包,所以單個編譯單元內的所有類相互間都是自動“友好”的。因此,我們也說友好元素擁有“包訪問”權限。
友好訪問允許我們將相關的類都組合到一個包里,使它們相互間方便地進行溝通。將類組合到一個包內以后(這樣便允許友好成員的相互訪問,亦即讓它們“交朋友”),我們便“擁有”了那個包內的代碼。只有我們已經擁有的代碼才能友好地訪問自己擁有的其他代碼。我們可認為友好訪問使類在一個包內的組合顯得有意義,或者說前者是后者的原因。在許多語言中,我們在文件內組織定義的方式往往顯得有些牽強。但在Java中,卻強制用一種頗有意義的形式進行組織。除此以外,我們有時可能想排除一些類,不想讓它們訪問當前包內定義的類。
對于任何關系,一個非常重要的問題是“誰能訪問我們的‘私有’或`private`代碼”。類控制著哪些代碼能夠訪問自己的成員。沒有任何秘訣可以“闖入”。另一個包內推薦可以聲明一個新類,然后說:“嗨,我是Bob的朋友!”,并指望看到Bob的`protected`(受到保護的)、友好的以及`private`(私有)的成員。為獲得對一個訪問權限,唯一的方法就是:
(1) 使成員成為`public`(公共的)。這樣所有人從任何地方都可以訪問它。
(2) 變成一個“友好”成員,方法是舍棄所有訪問指示符,并將其類置于相同的包內。這樣一來,其他類就可以訪問成員。
(3) 正如以后引入“繼承”概念后大家會知道的那樣,一個繼承的類既可以訪問一個`protected`成員,也可以訪問一個`public`成員(但不可訪問`private`成員)。只有在兩個類位于相同的包內時,它才可以訪問友好成員。但現在不必關心這方面的問題。
(4) 提供“訪問器/變化器”方法(亦稱為“獲取/設置”方法),以便讀取和修改值。這是OOP環境中最正規的一種方法,也是Java Beans的基礎——具體情況會在第13章介紹。
## 5.2.2 `public`:接口訪問
使用`public`關鍵字時,它意味著緊隨在`public`后面的成員聲明適用于所有人,特別是適用于使用庫的客戶程序員。假定我們定義了一個名為`dessert`的包,其中包含下述單元(若執行該程序時遇到困難,請參考第3章3.1.2小節“賦值”):
```
//: Cookie.java
// Creates a library
package c05.dessert;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
void foo() { System.out.println("foo"); }
} ///:~
```
請記住,`Cookie.java`必須駐留在名為`dessert`的一個子目錄內,而這個子目錄又必須位于由`CLASSPATH`指定的`C05`目錄下面(`C05`代表本書的第5章)。不要錯誤地以為Java無論如何都會將當前目錄作為搜索的起點看待。如果不將一個`.`作為`CLASSPATH`的一部分使用,Java就不會考慮當前目錄。
現在,假若創建使用了`Cookie`的一個程序,如下所示:
```
//: Dinner.java
// Uses the library
import c05.dessert.*;
public class Dinner {
public Dinner() {
System.out.println("Dinner constructor");
}
public static void main(String[] args) {
Cookie x = new Cookie();
//! x.foo(); // Can't access
}
} ///:~
```
就可以創建一個`Cookie`對象,因為它的構造器是`public`的,而且類也是`public`的(公共類的概念稍后還會進行更詳細的講述)。然而,`foo()`成員不可在`Dinner.java`內訪問,因為`foo()`只有在`dessert`包內才是“友好”的。
(1) 默認包
大家可能會驚訝地發現下面這些代碼得以順利編譯——盡管它看起來似乎已違背了規則:
```
//: Cake.java
// Accesses a class in a separate
// compilation unit.
class Cake {
public static void main(String[] args) {
Pie x = new Pie();
x.f();
}
} ///:~
```
在位于相同目錄的第二個文件里:
```
//: Pie.java
// The other class
class Pie {
void f() { System.out.println("Pie.f()"); }
} ///:~
```
最初可能會把它們看作完全不相干的文件,然而`Cake`能創建一個`Pie`對象,并能調用它的`f()`方法!通常的想法會認為`Pie`和`f()`是“友好的”,所以不適用于`Cake`。它們確實是友好的——這部分結論非常正確。但它們之所以仍能在`Cake.java`中使用,是由于它們位于相同的目錄中,而且沒有明確的包名。Java把象這樣的文件看作那個目錄“默認包”的一部分,所以它們對于目錄內的其他文件來說是“友好”的。
## 5.2.3 `private`:不能接觸!
`private`關鍵字意味著除非那個特定的類,而且從那個類的方法里,否則沒有人能訪問那個成員。同一個包內的其他成員不能訪問`private`成員,這使其顯得似乎將類與我們自己都隔離起來。另一方面,也不能由幾個合作的人創建一個包。所以`private`允許我們自由地改變那個成員,同時毋需關心它是否會影響同一個包內的另一個類。默認的“友好”包訪問通常已經是一種適當的隱藏方法;請記住,對于包的用戶來說,是不能訪問一個“友好”成員的。這種效果往往能令人滿意,因為默認訪問是我們通常采用的方法。對于希望變成`public`(公共)的成員,我們通常明確地指出,令其可由客戶程序員自由調用。而且作為一個結果,最開始的時候通常會認為自己不必頻繁使用`private`關鍵字,因為完全可以在不用它的前提下發布自己的代碼(這與C++是個鮮明的對比)。然而,隨著學習的深入,大家就會發現`private`仍然有非常重要的用途,特別是在涉及多線程處理的時候(詳情見第14章)。
下面是應用了`private`的一個例子:
```
//: IceCream.java
// Demonstrates "private" keyword
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
public class IceCream {
public static void main(String[] args) {
//! Sundae x = new Sundae();
Sundae x = Sundae.makeASundae();
}
} ///:~
```
這個例子向我們證明了使用`private`的方便:有時可能想控制對象的創建方式,并防止有人直接訪問一個特定的構造器(或者所有構造器)。在上面的例子中,我們不可通過它的構造器創建一個`Sundae`對象;相反,必須調用`makeASundae()`方法來實現(注釋③)。
③:此時還會產生另一個影響:由于默認構造器是唯一獲得定義的,而且它的屬性是`private`,所以可防止對這個類的繼承(這是第6章要重點講述的主題)。
若確定一個類只有一個“助手”方法,那么對于任何方法來說,都可以把它們設為`private`,從而保證自己不會誤在包內其他地方使用它,防止自己更改或刪除方法。將一個方法的屬性設為`private`后,可保證自己一直保持這一選項(然而,若一個引用被設為`private`,并不表明其他對象不能擁有指向同一個對象的`public`引用。有關“別名”的問題將在第12章詳述)。
## 5.2.4 `protected`:“友好的一種”
`protected`(受到保護的)訪問指示符要求大家提前有所認識。首先應注意這樣一個事實:為繼續學習本書一直到繼承那一章之前的內容,并不一定需要先理解本小節的內容。但為了保持內容的完整,這兒仍然要對此進行簡要說明,并提供相關的例子。
`protected`關鍵字為我們引入了一種名為“繼承”的概念,它以現有的類為基礎,并在其中加入新的成員,同時不會對現有的類產生影響——我們將這種現有的類稱為“基類”或者“基本類”(Base Class)。亦可改變那個類現有成員的行為。對于從一個現有類的繼承,我們說自己的新類“擴展”(`extends`)了那個現有的類。如下所示:
```
class Foo extends Bar {
```
類定義剩余的部分看起來是完全相同的。
若新建一個包,并從另一個包內的某個類里繼承,則唯一能夠訪問的成員就是原來那個包的`public`成員。當然,如果在相同的包里進行繼承,那么繼承獲得的包能夠訪問所有“友好”的成員。有些時候,基類的創建者喜歡提供一個特殊的成員,并允許訪問派生類。這正是`protected`的工作。若往回引用5.2.2小節“`public`:接口訪問”的那個`Cookie.java`文件,則下面這個類就不能訪問“友好”的成員:
```
//: ChocolateChip.java
// Can't access friendly member
// in another class
import c05.dessert.*;
public class ChocolateChip extends Cookie {
public ChocolateChip() {
System.out.println(
"ChocolateChip constructor");
}
public static void main(String[] args) {
ChocolateChip x = new ChocolateChip();
//! x.foo(); // Can't access foo
}
} ///:~
```
對于繼承,值得注意的一件有趣的事情是倘若方法`foo()`存在于類`Cookie`中,那么它也會存在于從`Cookie`繼承的所有類中。但由于`foo()`在外部的包里是“友好”的,所以我們不能使用它。當然,亦可將其變成`public`。但這樣一來,由于所有人都能自由訪問它,所以可能并非我們所希望的局面。若象下面這樣修改類`Cookie`:
```
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void foo() {
System.out.println("foo");
}
}
```
那么仍然能在包`dessert`里“友好”地訪問`foo()`,但從`Cookie`繼承的其他東西亦可自由地訪問它。然而,它并非公共的(`public`)。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物