6、封裝

面向對象設計中的一個基本問題:“如何區分變動的事物和不變的事物”。

這個問題對於類庫而言尤其重要。類庫的使用者必須依賴他們所使用的那部分類庫,並且知道如果使用了類庫的新版本,不需要改寫代碼。另一方面,類庫的開發者必須有修改和改進類庫的自由,並保證客戶代碼不會受這些改動影響。

爲了解決這一問題,Java 提供了訪問修飾符供類庫開發者指明哪些對於客戶端程序員是可用的,哪些是不可用的。訪問控制權限的等級,從"最大權限"到"最小權限"依次是:public,protected,包訪問權限(沒有關鍵字)和 private。

1、包的概念

如何將類庫組件捆綁到一個內聚到類庫單元中?
Java 中通過 package 關鍵字加以控制,類是在相同包下還是不同包下會影響訪問修飾符。

使用 import 關鍵字。如果需要導入某個類,就需要在 import 語句中聲明。

一個 Java 源代碼文件稱爲一個編譯單元(有時也稱翻譯單元)。每個編譯單元的文件名後綴必須是 .java。在編譯單元中可以有一個 public 類,它的類名必須與文件名相同(包括大小寫,但不包括後綴名 .java)。每個編譯單元中只能有一個 public 類,否則編譯器不接受。如果這個編譯單元中還有其他類,那麼在包之外是無法訪問到這些類的,因爲它們不是 public 類,此時它們支持主 public 類。

2、代碼的組織

當編譯一個 .java 文件時,.java 文件的每個類都會有一個輸出文件。每個輸出的文件名和 .java 文件中每個類的類名相同,只是後綴名是 .class。

在 Java 中,可運行程序是一組 .class 文件,它們可以打包壓縮成一個 Java 文檔文件(JAR,使用 jar 文檔生成器)。Java 解釋器負責查找、加載和解釋這些文件。

如果把這些組件集中在一起,就需要使用關鍵字 package。

Java 包名按慣例一律小寫,即使中間的單詞也需要小寫,與駝峯命名不同

3、創建獨一無二的包名

一個包從未真正被打包成單一的文件,它可以由很多 .class 文件構成。

將所有的文件放在一個子目錄還解決了其他的兩個問題:創建獨一無二的包名和查找可能隱藏於目錄結構某處的類。這是通過將 .class 文件所在的路徑位置編碼成 package 名稱來實現的。按照慣例,package 名稱是類的創建者的反順序的 Internet 域名。

把 package 名稱分解成你機器上的一個目錄,所以當 Java 解釋器必須要加載一個 .class 文件時,它能定位到 .class 文件所在的位置。首先,它找出環境變量 CLASSPATH(通過操作系統設置,有時也能通過 Java 的安裝程序或基於 Java 的工具設置)。CLASSPATH 包含一個或多個目錄,用作查找 .class 文件的根目錄。從根目錄開始,Java 解釋器獲取包名並將每個句點替換成反斜槓,生成一個基於根目錄的路徑名(包名 foo.bar.baz 變成 foo\bar\baz 或 foo/bar/baz 或其它,取決於你的操作系統)。然後這個路徑與 CLASSPATH 的不同項連接,解釋器就在這些目錄中查找與你所創建的類名稱相關的 .class 文件(解釋器還會查找某些涉及 Java 解釋器所在位置的標準目錄)。

但是在使用 JAR 文件時,有點不一樣。你必須在類路徑寫清楚 JAR 文件的實際名稱,不能僅僅是 JAR 文件所在的目錄。因此,對於一個名爲 grape.jar 的 JAR 文件,類路徑應包括:

CLASSPATH=.;D\JAVA\LIB;C:\flavors\grape.jar

4、定製工具庫

以一個 CLASSPATH 位置開始

當創建一個包時,包名就隱含了目錄結構。這個包必須位於包名指定的目錄中,該目錄必須在以 CLASSPATH 開始的目錄中可以查詢到。

5、使用 import 改變行爲

條件編譯還有其他的用途。調試是一個很常見的用途,調試功能在開發過程中是開啓的,在發佈的產品中是禁用的。可以通過改變導入的 package 來實現這一目的,修改的方法是將程序中的代碼從調試版改爲發佈版。這個技術可用於任何種類的條件代碼。

6、訪問權限修飾符

Java 訪問權限修飾符 public,protected 和 private 位於定義的類名,屬性名和方法名之前。每個訪問權限修飾符只能控制它所修飾的對象。

如果不提供訪問修飾符,就意味着"包訪問權限"。所以無論如何,萬物都有某種形式的訪問控制權。

6.1、包訪問權限

默認訪問權限沒有關鍵字,通常被稱爲包訪問權限(有時也稱爲 friendly)。這意味着當前包中的所有其他類都可以訪問那個成員。對於這個包之外的類,這個成員看上去是 private 的。

取得對成員的訪問權的唯一方式是:

  1. 使成員成爲 public。那麼無論是誰,無論在哪,都可以訪問它。
  2. 賦予成員默認包訪問權限,不用加任何訪問修飾符,然後將其他類放在相同的包內。這樣,其他類就可以訪問該成員。
  3. 繼承的類既可以訪問 public 成員,也可以訪問 protected 成員(但不能訪問 private 成員)。只有當兩個類處於同一個包內,它纔可以訪問包訪問權限的成員。
  4. 提供訪問器(accessor)和修改器(mutator)方法(有時也稱爲"get/set" 方法),從而讀取和改變值。

6.2、public: 接口訪問權限

當你使用關鍵字 public,就意味着緊隨 public 後聲明的成員對於每個人都是可用的。

如果幾個類在相同的目錄中且沒有給自己設定明確的包名,Java 把這樣的文件看作是隸屬於該目錄的默認包中,因此它們爲該目錄中所有的其他文件都提供了包訪問權限。

6.3、private: 你無法訪問

關鍵字 private 意味着除了包含該成員的類,其他任何類都無法訪問這個成員。

使用 private 是非常重要的,尤其是在多線程環境中。

class Sundae {
    private Sundae() {}//構造器用private修飾
    static Sundae makeASundae() {
        return new Sundae();
    }
}

public class IceCream {
    public static void main(String[] args) {
        //- Sundae x = new Sundae();
        Sundae x = Sundae.makeASundae();
    }
}

然而,不能因爲類中某個對象的引用是 private,就認爲其他對象也無法擁有該對象的 public 引用(參見附錄:對象傳遞和返回)

6.4、protected: 繼承訪問權限

有時,基類的創建者會希望某個特定成員能被繼承類訪問,但不能被其他類訪問。這時就需要使用 protected。protected 也提供包訪問權限,也就是說,相同包內的其他類可以訪問 protected 元素。

6.5、包訪問權限 Vs Public 構造器

在一個具有包訪問權限的類中定義一個 public 的構造器並不能真的使這個構造器成爲 public,在聲明的時候就應該標記爲編譯時錯誤。

7、接口和實現

訪問控制通常被稱爲實現的隱藏。
將數據和方法包裝進類中並把具體實現隱藏被稱作是封裝。其結果就是一個同時帶有特徵和行爲的數據類型。

訪問控制在數據類型內部劃定了邊界,基於以下原因:

  • 確立客戶端程序員可以使用和不能使用的邊界。可以在結構中建立自己的內部機制而不必擔心客戶端程序員偶爾將內部實現作爲他們可以使用的接口的一部分。
  • 將接口與實現分離。如果在一組程序中使用結構,而客戶端程序員只能向 public 接口發送消息的話,那麼就可以自由地修改任何不是 public 的事物(例如包訪問權限,protected,或 private 修飾的事物),卻不會破壞客戶端代碼。

爲了清晰起見,你可以採用一種創建類的風格:public 成員放在類的開頭,接着是 protected 成員,包訪問權限成員,最後是 private 成員。這麼做的好處是類的使用者可以從頭讀起,首先會看到對他們而言最重要的部分(public 成員,因爲可以從文件外訪問它們),直到遇到非 public 成員時停止閱讀

8、類訪問權限

  1. 每個編譯單元(即每個文件)中只能有一個 public 類。這表示,每個編譯單元有一個公共的接口用 public
    類表示。該接口可以包含許多支持包訪問權限的類。一旦一個編譯單元中出現一個以上的 public 類,編譯就會報錯。
  2. public 類的名稱必須與含有該編譯單元的文件名相同,包括大小寫。所以對於 Widget 來說,文件名必須是Widget.java,不能是 widget.java 或 WIDGET.java。再次強調,如果名字不匹配,編譯器會報錯。

當你創建了一個包訪問權限的類,把類中的屬性聲明爲 private 仍然是有意義的——應該儘可能將所有屬性都聲明爲 private,但是通常把方法聲明成與類(包訪問權限)相同的訪問權限也是合理的。

注意,類既不能是 private 的(這樣除了該類自身,任何類都不能訪問它),也不能是 protected 的。所以對於類的訪問權限只有兩種選擇:包訪問權限或者 public。爲了防止類被外界訪問,可以將所有的構造器聲明爲 private,這樣只有你自己能創建對象(在類的 static 成員中)

// hiding/Lunch.java
// Demonstrates class access specifiers. Make a class
// effectively private with private constructors:

class Soup1 {
    private Soup1() {}

    public static Soup1 makeSoup() { // [1]
        return new Soup1();
    }
}

class Soup2 {
    private Soup2() {}

    private static Soup2 ps1 = new Soup2(); // [2]

    public static Soup2 access() {
        return ps1;
    }

    public void f() {}
}
// Only one public class allowed per file:
public class Lunch {
    void testPrivate() {
        // Can't do this! Private constructor:
        //- Soup1 soup = new Soup1();
    }

    void testStatic() {
        Soup1 soup = Soup1.makeSoup();
    }

    void testSingleton() {
        Soup2.access().f();
    }
}

可以像 [1] 那樣通過 static 方法創建對象,也可以像 [2] 那樣先創建一個靜態對象,當用戶需要訪問它時返回對象的引用即可。Soup2 用到了所謂的設計模式。這種模式叫做單例模式,因爲它只允許創建類的一個對象。

如果你不顯式地創建構造器,編譯器會自動爲你創建一個無參構造器(沒有參數的構造器)。如果我們編寫了無參構造器,那麼編譯器就不會自動創建構造器了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章