1. 程式人生 > >封裝與訪問許可權修飾符

封裝與訪問許可權修飾符

一.java的四種訪問許可權
1.幾個注意的細節:

  • 類的訪問許可權只有兩種:public和default(預設包訪問許可權)
  • java編譯單元概念:一個.java檔案是一個編譯單元,每個編譯單元最多隻能有一個public類,而且該public類名稱必須與檔案的名稱相同(包括大小寫,但不包括檔案字尾名.java).如果該編譯單元內還有其他類,那麼這些類只能是default許可權,不能被包外訪問,而且這些類的主要作用是為public提供支援的(內部類算另外一個編譯單元,因為內部類會被編譯成另外一個.class檔案)
  • 建構函式也可以私有化(private),然後在類中例項化一個物件,再通過方法返回該物件
  • 成員變數(在類裡面宣告的變數)在宣告時可以不給它初始化,編譯器會自動給這個成員變數初始化,但區域性變數(在方法裡面宣告的變數)在宣告時一定要給它初始化,因為編譯器不會自動給區域性變數初始化,任何變數在使用之前必須對它進行初始化。

2.Java有四種訪問許可權, 其中三種有訪問許可權修飾符,分別為private,protected和public,還有一種不帶任何修飾符的預設訪問許可權(也叫包訪問許可權)。

  • private: Java語言中對訪問許可權限制的最窄的修飾符,一般稱之為“私有的”。被其修飾的類、屬性以及方法只能被該類的物件訪問,其子類不能訪問,更不能允許跨包訪問。
  • default:即不加任何訪問修飾符,通常稱為”預設訪問許可權”。該模式下,只允許在同一個包中進行訪問。
  • protect: 介於public 和 private 之間的一種訪問修飾符,一般稱之為“繼承訪問許可權”。被其修飾的類、屬性以及方法只能被類本身的方法及子類訪問,即使子類在不同的包中也可以訪問。
  • public: Java語言中訪問限制最寬的修飾符,一般稱之為“公共的”。被其修飾的類、屬性以及方法不僅可以跨類訪問,而且允許跨包(package)訪問。

3.下面用表格的形式來展示四種訪問許可權之間的異同點.表格如下:
(1)public修飾的類:

\ 當前類 同一個包的非子類 不同包的非子類 同一包的子類 不同包的子類
public
protected
default
private

(2)預設包訪問許可權的類(對於一個包裡面的包訪問許可權類A,在其他package的類裡面import類A的時候就已經出錯了。所以,其他package普通類根本就訪問不了類A,其他package的類也成為不了類A的子孫類(即預設訪問許可權的類的子類只能在同一包下)):

\ 當前類 同一個包的非子類 不同包的非子類 同一包的子類 不同包的子類
public
protected
default
private

(3)詳解protected訪問許可權:
說例子之前,我們應該先正確理解一句話:protected修飾的屬性或方法,表示在同一個包內或者不同包的子類可以訪問。
解析:
“不同包中的子類可以訪問”(子類可訪問,意思是用子類的物件去引用方法),是指當兩個類不在同一個包中的時候,繼承自父類的子類內部且主調(呼叫者)為子類的引用時才能訪問父類用protected修飾的成員屬性或方法。 在子類內部,主調為父類的引用時並不能訪問此protected修飾的成員(super關鍵字除外)。
我們用一個例子來仔細說明:

public class MyObject {

    public static void main(String[] args) {
        Object o1 = new Object();//Object物件有一個clone()方法,該方法是protected的
        o1.clone();//此處報錯:The method clone() from the type Object is not visible
    }

}

解析:
子類物件建立的過程,其實是先載入其父類檔案,建立其父類物件之後,再載入子類檔案,建立子類物件,最後形成一個包裹結構的物件(見博文《繼承》中物件建立的記憶體分析).如果子類沒有覆蓋父類中的屬性和方法,則子類的屬性和方法只是對父類中同名屬性和方法的引用(也就是說沒被覆蓋的方法其實是父類的);如果子類覆蓋了父類中的屬性和方法,則被覆蓋的屬性和方法是對自己的引用(也就是說被覆蓋的方法才是子類自己的).

public class MyObject2 {

    public static void main(String[] args) throws CloneNotSupportedException {
        MyObject2 obj = new MyObject2();
        obj.clone();//編譯通過
    }

    String name = "xxx";
    public void shuchu() throws CloneNotSupportedException{
        clone();
    }

}

解析:
子類引用可以直接呼叫父類的protected方法,而且子類中可以直接呼叫父類方法

Test1.java
class MyObject {}

public class Test {
    public static void main(String[] args) {
       MyObject obj = new MyObject();
       obj.clone(); // 這裡編譯報錯:The method clone() from the type Object is not visible
    }
}

我們知道clone()是Object類的protected方法.這說明,該方法可以被同包(java.lang)下和它(java.lang.Object)的子類訪問.這裡MyObject類是Object的子類(預設繼承java.lang.Object).
同樣Test1也是java.lang.Object的子類。但是,在Test類中,主調卻為MyObject物件,因此編譯報錯

Test2.java
class MyObject2 {
    protected Object clone() throws CloneNotSupportedException {
       return super.clone();
    }
}

public class Test2 {
    public static void main(String[] args) throws CloneNotSupportedException {
       MyObject2 obj = new MyObject2();
       obj.clone(); // 編譯通過
    }
}

這裡,我們在MyObject2類中覆蓋父類的clone()方法,在另一個類Test2中呼叫clone()方法,編譯通過。
編譯通過的原因顯而易見,當你在MyObject2類中覆蓋clone()方法時,MyObject2類和Test2類在同一個包下,所以此protected方法對Test2類可見。

Test3.java
package 1
public class MyObject3 {
protected Object clone() throws CloneNotSupportedException {
       return super.clone();
    }
}

package 2
public class Test3 extends MyObject3 {
    public static void main(String args[]) {
       MyObject3 obj = new MyObject3();
       obj.clone(); // Compile error.
       Test3 tobj = new Test3();
       tobj.clone();// Complie OK.
    }
}

這裡我用Test3類繼承MyObject3,注意這兩個類是不同包的,否則就是示例2的情形。在Test3類中呼叫Test3類的例項tobj的clone()方法,編譯通過。而同樣呼叫MyObject3類的例項obj的clone()方法,編譯錯誤!
意想不到的結果,protected方法不是可以被繼承類訪問嗎?
必須明確,類Test3確實是繼承了類MyObject3(包括它的clone方法),所以在類Test3中可以呼叫自己的clone方法。但類MyObject3的protected方法對其不同包子類Test3來說,是不可見的。

二.面向物件的三大特性之封裝

  1. 封裝的定義:封裝從字面上來理解就是包裝的意思,專業點就是資訊隱藏,是指利用抽象資料型別將資料和基於資料的操作封裝在一起,使其構成一個不可分割的獨立實體,資料被保護在抽象資料型別的內部,儘可能地隱藏內部的細節,只保留一些對外介面使之與外部發生聯絡。系統的其他物件只能通過包裹在資料外面的已經授權的操作來與這個封裝的物件進行交流和互動。也就是說使用者是無需知道物件內部的細節(當然也無從知道),但可以通過該物件對外的提供的介面來訪問該物件。
  2. 封裝的好處:
    (1)良好的封裝能夠減少耦合
    (2)類內部的結構可以自由修改
    (3)可以對成員進行更精確的控制
    (4)隱藏資訊,實現細節
  3. 實現封裝的一般步驟:
    (1)修改屬性的可見性來限制對屬性的訪問。一般把屬性宣告為private
    (2)為每個屬性建立一對賦值方法和取值方法,用於對這些屬性的訪問。(get,set方法)
    (3)在賦值和取值方法中,加入對屬性的存取限制。