[JAVA] Java物件導向三大特徵:封裝、繼承、多型

老夫不正經發表於2020-04-01

Java物件導向三大特徵:封裝、繼承、多型

物件導向三大特徵:封裝、繼承、多型

高內聚和低耦合

物件導向的最終目的是要構建強健、安全、高效的專案,也就是要實現專案的高內聚和低耦合:

  • 高內聚:把該模組的內部資料,功能細節隱藏在模組內部,不允許外界直接干預;只能通過公開的介面訪問;
  • 低耦合:該模組只需要給外界暴露少量功能方法;模組之間相互依賴的程度不高;

封裝

什麼是封裝

  1. 把物件的狀態和行為看成一個統一的整體,將二者存放在一個獨立的模組中,比如:類;
  2. 細節隱藏, 把不想對外公開的實現細節隱藏起來,使用private私有化使其私有化,向外暴露public方法,保證呼叫者安全訪問,不會出現因為越界導致本不應該出現的問題出現;

封裝的好處:

  1. 呼叫者能夠正確、方便地使用系統功能,有效地防止呼叫者隨意修改系統屬性。
  2. 把特定的功能封裝起來,提高功能的重用性。
  3. 降低功能元件之間的耦合性,即使一個模組的實現細節發生改變,只要保證對外暴露的介面或者方法保持不變,就不會影響到其他呼叫者。

訪問許可權修飾符

應封裝的隱藏細節的理念,java提供了訪問許可權修飾符來控制呼叫者的訪問許可權,詳情如下:

private:屬於類訪問許可權,表示私有的,只能在當前類中訪問,使用private修飾的類、方法、欄位,在’離開當前類的作用域之後,呼叫者就不能直接訪問。

(預設):其實就是什麼都不寫,其屬於包訪問許可權,表示包私有的,呼叫者的包必須和當前類(使用預設修飾)的包相同才能訪問。

protected:屬於子類訪問許可權,表示受保護的,使用private修飾的類、方法、欄位**,**不僅同包中類可以訪問,而且即使不同包,但是如果有繼承關係,也可以訪問。

public:表示全域性的公共訪問許可權,使用private修飾的類、方法、欄位,在當前專案中任何地方都可以訪問。介面(interface)中的方法預設都是public的。

訪問許可權修飾符

一般情況下,類的欄位都使用private修飾;封裝了實現細節的方法,一般也使用private修飾,因為不希望呼叫者直接訪問其實現細節,而是要通過公開的public方法間接呼叫。

// 公開給呼叫者訪問的public方法
public void doWork(){
    methodA();
    methodB();
    methodC();
}
// 三個方法分別封裝了部分實現細節
private methodA(){}
private methodB(){}
private methodC(){}
複製程式碼

很少會使用(預設),即使要使用,也僅僅是暴露給同包中的其他類訪問;protected很多時候出現在繼承關係中,父類只希望被子類訪問的欄位和方法時;

Java 中的繼承

從物件導向的角度上說,繼承是一種從一般到特殊的關係,是一種**“is a”**的關係,即子類是對父類的擴充,是一種特殊的父類,比如:狗是動物的一種特殊情況,狗屬於動物;在這個例子中,動物是父類,狗是子類,狗繼承了動物的特徵和行為,並在動物的特徵和行為的基礎之上擴充自己的特徵和行為,構成了狗這種特殊的動物。

所以可以基於父類並對其加以擴充,產生新的子類定義,這就是繼承;子類可以通過繼承獲得父類原有的欄位和方法,也可以增加父類所沒有的欄位和方法,更是可以覆寫父類中的允許被子類覆蓋的欄位或者方法。

在Java中使用”extends”關鍵字來表示子類和父類之間的繼承關係;在Java中,類之間的繼承關係只允許單繼承,不允許多繼承,一個類只能有一個直接父類。

語法格式:

public class 子類類名 extends 父類類名{		
    編寫自己特有的狀態和行為
}
複製程式碼

也就是說一個類只能有一個直接的父類,不能出現類A同時繼承於類B和類C。即便不允許多繼承,但是多重繼承是被允許的。以下是一個多重繼承的例子:

動物有胎生動物和卵生動物之分,胎生動物有老虎,老虎又分華南虎,東北虎,孟加拉虎等。在繼承體系中可以這樣來表示:

華南虎——》老虎——》胎生動物——》動物

多重繼承

最終,華南虎通過多重繼承獲得了老虎、胎生動物、動物的特徵和行為。

繼承的優點:

  1. 有效的解決了程式碼的重用問題,使程式碼擴充更加靈活;
  2. 通過從繼承關係,可以從始至終完整的體現出一個應用體系,邏輯更加清晰;
  3. 一般在開發中是先有多個自定義類,再從多個類中寫共同的程式碼,抽象生成一個父類,然後子類都繼承於它。使用web框架開發時,也會更多的使用繼承來擴充框架的功能,以適應不同的業務需求。

繼承體系

子類可以繼承父類的哪些成員(根據訪問修飾符來判斷):

  1. 父類中使用protected、public修飾的成員;
  2. 父類和子類在同一個包中,父類中預設修飾符的成員可以被子類繼承;

不能繼承的是:

  1. 父類中使用private修飾的成員;
  2. 父類的構造器,子類也不能繼承,因為構造器必須和當前的類名相同,父類的構造器是父類的名稱,子類的構造器是子類的名稱;

方法重寫和方法過載

方法重寫(Override):從父類繼承的方法(行為)不符合子類的功能需求,那此時子類就需要重新實現父類的方法,並重寫方法體,以實現子類需求。

方法重寫的原則:一同兩小一大

一同

  • 方法簽名必須相同。 方法簽名= 方法名 + 方法的引數列表,引數型別、引數個數、引數順序都必須一致

兩小

  1. 子類方法的返回值型別和父類方法的返回型別相同或者是其子類,子類可以返回一個更加具體的子類.
  2. 子類方法宣告丟擲的異常型別和父類方法宣告丟擲的異常型別相同或者是其子類,子類方法中宣告丟擲的異常小於或等於父類方法宣告丟擲異常型別;子類方法可以同時宣告丟擲多個屬於父類方法宣告丟擲異常類的子類(RuntimeException型別除外,RuntimeException是個特例);
// 父類
class SuperClass {  
    protected void work() throws Exception {}
}

// 子類
class SubClass extends SuperClass {  
    @Override  
    protected void work() throws NullPointerException, NumberFormatException {}
    
}
複製程式碼

一大:

  • 子類方法的訪問許可權與父類方法訪問許可權相同或者更大;而private方法不能被子類所繼承,也就不會被覆蓋。

在重寫方法父類方法時,使用@Override註解修飾方法,若是重寫錯誤,就會報編譯錯誤,是一大開發利器;這裡需要注意的是隻有方法會被重寫,欄位則沒有重寫一說。

方法過載(Overload): 在同一個類中,方法名字相同,但是因方法引數列表不同而又不同的實現,這樣的機制稱為方法過載,其實現原則是:兩同一不同,返回值並不計入其中。

兩同:相同類,方法名(只是方法名,不是方法簽名)相同

一不同:方法引數列表不同,包括引數型別、引數個數、引數順序都必須一致

protected void work() {}

protected int work(String str) {  return 0;}
複製程式碼

方法過載(Overload)和方法重寫(Override)兩者只是名字上比較接近,其本身並沒有關係。

this 關鍵字

this關鍵字表示當前物件,當前物件就是this所在的這個物件。this主要存在於兩個位置:

  • 構造器中: 就表示當前正在建立的物件
  • 方法中: 表示方法的呼叫者物件

當一個物件建立之後,JVM會為物件分配一個引用該物件自身的引用:this。this的使用是為了:

  1. 解決成員變數和方法引數、區域性變數之間的二義性,使用this可以顯式指向成員變數;
  2. 同類中例項方法間相互呼叫,雖然此時可以省略不寫,但還是建議不要省略,以提高程式碼的可讀性。
  3. 將this作為引數傳遞給另一個方法;
  4. 將this作為方法的返回值(鏈式方法程式設計);
  5. 構造器過載的互相呼叫,this([引數])必須寫在構造方法第一行;
  6. static不能和this一起使用;因為當位元組碼被載入進JVM的時候,static成員就已經存在了,但是此時物件很有可能還沒有被建立,沒有物件也就沒有this。

super 關鍵字

在物件中this表示當前物件,而super則表示當前物件的父類物件。在子類初始化過程中會建立子類物件,但在建立子類物件之前,會先建立父類物件;也就是說呼叫子類構造器之前,在子類構造器中會先呼叫父類的構造器,如果沒有顯式的呼叫父類構造器,那麼預設情況下會隱式的呼叫父類無引數構造器。

super關鍵字用於顯式呼叫父類方法、構造器和欄位;可以使用super解決子類隱藏了父類的欄位情況;在子類方法中,呼叫父類被覆蓋的方法;在子類構造器中,呼叫父類構造器。

class SuperClass {  
    public SuperClass() {}
}

class SubClass extends SuperClass {  
    public SubClass() {    
        super(); // 呼叫父類構造器  
    }
}
複製程式碼

父類構造器的不可或缺性:

  1. 如果父類不存在可以被子類訪問/呼叫的構造器,則子類就不可能存在。也就是必須要先有父類物件,而後才會有子類物件;
  2. 如果父類沒有提供無引數構造器,子類就必須顯式地通過super語句去呼叫父類帶引數的構造器。子類物件在初始化過程中,必須先呼叫父類構造器,而後再呼叫子類構造器。

繼承中的隱藏

上文中提到了隱藏的概念,繼承中的隱藏表示會忽略一些特徵和方法,比如靜態欄位和靜態方法:

  1. 滿足繼承的訪問許可權下,隱藏父類靜態方法:若子類定義的靜態方法的簽名和超類中的靜態方法簽名相同,那麼此時就是隱藏父類方法。注意:僅僅是在子類存在和父類一模一樣的靜態方法的情況下。
  2. 滿足繼承的訪問許可權下,隱藏父類欄位:若子類中定義的欄位和父類中的欄位名相同(忽略資料型別),此時是隱藏父類欄位,但是可以通過super訪問被隱藏的欄位。
  3. 隱藏本類欄位:若本類中的區域性變數名和欄位名相同,此時就是隱藏本類欄位,可以通過this訪問被隱藏的欄位。

無論是this,還是super,都不能和static一起使用。

Object 類

在Java中除去Object類之外的每一個類都有一個直接或間接的父類:Object類。也就是說除去Object類之外的類都是Object類的直接子類或間接子類。

比如:

class Person {}

class Student extends Person{}
複製程式碼

此時Student類的直接父類是Person,Person類的直接父類是Object類,Object類是Student類的間接父類。

Object類是Java的基類,Java中的類都是Object的直接或者間接的子類,Object本身是指物件的意思, 它是所有的物件都具有的共同的行為的抽象類,其他類都會直接或者間接繼承於Object類,然後也就擁有了Object類中的方法。

class SuperClass {} 

等價於 

class SuperClass extends Object {}
複製程式碼

Object類的常見方法:

  1. finalize() :當垃圾回收器確定不存在對該物件的更多引用時,也就是垃圾回收器會在回收物件之前,會先呼叫該方法;該方法我們一般不會呼叫。
  2. getClass() :返回當前物件的真實型別。
  3. hashCode(): 返回該物件的雜湊碼值,hashCode(雜湊碼值)決定了物件再雜湊表中的儲存位置,不同物件的儲存位置是不一樣的,所以hashCode也會是不一樣的。
  4. equals(Object obj) :用當前物件(this)和引數obj做比較,在Object類中的equals方法,比較物件的記憶體地址。官方建議:每個類都應該覆蓋equals方法,不要比較記憶體地址,而是去比較內容資料。
  5. toString():表示把一個物件轉換為字串,列印物件時,呼叫的就是物件的toString方法,預設情況下列印的是物件的十六進位制的hashCode值。 官方建議:應該每個類都應該覆蓋toString,返回我們真正關心的資料。

多型

通過上文,講清楚了繼承關係,繼承關係是一種”is a”(是一種)的關係,也就是說子類是父類的一種特殊情況;既然子類是一種特殊的父類,我們是否可以認為子類物件就是父類型別的物件。

考慮以下的程式碼:

Animal d = new Dog(); //建立一隻狗物件,型別是動物
Animal c = new Cat(); //建立一隻貓物件,型別是動物
複製程式碼

這個時候,多型就產生了。我們以下面的程式碼為例,詳細解釋什麼是多型:

Animal a = new Dog();
複製程式碼

在上例中,物件a具有兩種型別:

  • 編譯型別: 宣告物件變數的型別,Animal;表示把物件看作是什麼型別。
  • 執行型別: 物件的真實型別,Dog;執行型別--->物件的真實型別。

編譯型別必須是執行型別的父類或與之相同,當編譯型別和執行型別不同的時候,多型就產生了。所謂多型是指物件具有多種形態,物件可以存在不同的形式:

Animal a = null;
a = new Dog(); //a此時表示Dog型別的形態
a = new Cat(); //a此時表示Cat型別的形態
複製程式碼

多型可以是類和類之間的繼承關係,也可以是介面和實現類間的實現關係,一般情況下指的都是介面和實現類間的實現關係。

多型的特點:把子類物件賦給父類型別的變數,在執行時期會表現出具體的子類特徵,比如父類型別的變數呼叫子類的方法。

多型的好處:通過一個例子呈現

需求:給飼養員提供一個餵養動物的方法,用於餵養動物。

  • 如果沒有多型,針對於不同型別的動物,得提供不同的餵養方法。可擴充性差,方法重用性低,不優雅。
  • 存在多型:提供統一的餵養方法,大大減輕了飼養員的工作量。從上述例子,不難發現:當把不同的子類物件都當作父類型別來看待,可以遮蔽不同子類物件之間的實現差異,從而寫出通用的程式碼達到通用程式設計,以適應需求的不斷變化。

多型

資料型別轉換

基本資料型別轉換:大和小表示的是可儲存的容量範圍。

自動型別轉換:把小型別的資料賦給大型別的變數:

  • byte b = 12; byte是1個位元組
  • int i = b; int是4個位元組

強制型別轉換:把大型別的資料賦給小型別的變數。

  • short s = (short) i ;short是2個位元組

**引用型別的轉換:**引用型別的大和小,指的是父類和子類的關係。

自動型別轉換: 把子類物件賦給父類變數(多型)。

Animal a = new Dog();
Object obj = new Dog();
複製程式碼

強制型別轉換:把父類型別物件賦給子類型別變數(物件的真實型別是子類型別)。

Animal a = new Dog();
Dog d = (Dog)a;
複製程式碼

instanceof 運算子

instanceof 運算子: 判斷該物件是否是某一個類的例項。

語法格式:

boolean b = 物件A instanceof 類B; 
// 判斷 A物件是否是 B類的例項,如果是,返回true.
複製程式碼

instanceof運算子:

  • 若物件是類的例項返回true。
  • 若物件是類的父類的例項也返回true。

組合關係(has a)

在繼承關係中,子類可以繼承到父類中部分的成員,那麼此時子類是可以修改到父類的資訊的,此時的繼承關係破壞了封裝,讓子類擁有了本不該具有的功能。那麼這時可以使用”**包含關係”(has a)**的組合關係。

可以這麼理解組合關係:把另一個類當作屬性來獲取其特徵和行為。

比如:需求是:我想擁有天子的權力;

  1. 方式1: 當太子,此時表現的是繼承關係;
  2. 方式2: 學曹操挾天子以令諸侯,挾持天子,此時是組合/包含關係。

思考:如果A類想要得到B的功能行為,如若A類是B類的一種特殊情況,就應該採用繼承來實現,否則使用組合方式。

class Cat {    
    public void eatMouse() {  	
        System.out.println("貓吃老鼠"); 
    }	
}

class XiaoMing {	
    // 把cat類當作屬性引入  
    private  Cat cat;   
    public XiaoMing() {  	
        cat = new Cat();  
    }	
    
    public void killMouse() {  		
        cat.eatMouse();  
    }  
}
複製程式碼

完結。老夫雖不正經,但老夫一身的才華

相關文章