高內聚和低耦合
面向對象的最終目的是要構建強健、安全、高效的項目,也就是要實現項目的高內聚和低耦合:
- 高內聚:把該模塊的內部數據,功能細節隱藏在模塊內部,不允許外界直接干預;只能通過公開的接口訪問;
- 低耦合:該模塊只需要給外界暴露少量功能方法;模塊之間相互依賴的程度不高;
封裝
什麼是封裝
- 把對象的狀態和行為看成一個統一的整體,將二者存放在一個獨立的模塊中,比如:類;
- 細節隱藏, 把不想對外公開的實現細節隱藏起來,使用private私有化使其私有化,向外暴露public方法,保證調用者安全訪問,不會出現因為越界導致本不應該出現的問題出現;
封裝的好處:
- 調用者能夠正確、方便地使用系統功能,有效地防止調用者隨意修改系統屬性。
- 把特定的功能封裝起來,提高功能的重用性。
- 降低功能組件之間的耦合性,即使一個模塊的實現細節發生改變,只要保證對外暴露的接口或者方法保持不變,就不會影響到其他調用者。
訪問權限修飾符
應封裝的隱藏細節的理念,java提供了訪問權限修飾符來控制調用者的訪問權限,詳情如下:
private:屬於類訪問權限,表示私有的,只能在當前類中訪問,使用private修飾的類、方法、字段,在’離開當前類的作用域之後,調用者就不能直接訪問。
(缺省):其實就是什麼都不寫,其屬於包訪問權限,表示包私有的,調用者的包必須和當前類(使用缺省修飾)的包相同才能訪問。
protected:屬於子類訪問權限,表示受保護的,使用private修飾的類、方法、字段,不僅同包中類可以訪問,而且即使不同包,但是如果有繼承關係,也可以訪問。
public:表示全局的公共訪問權限,使用private修飾的類、方法、字段,在當前項目中任何地方都可以訪問。接口(interface)中的方法默認都是public的。
一般情況下,類的字段都使用private修飾;封裝了實現細節的方法,一般也使用private修飾,因為不希望調用者直接訪問其實現細節,而是要通過公開的public方法間接調用。
<code>// 公開給調用者訪問的public方法
public void doWork()
{
methodA();
methodB();
methodC();
}
// 三個方法分別封裝了部分實現細節
private methodA(){}
private methodB(){}
private methodC(){}/<code>
很少會使用(缺省),即使要使用,也僅僅是暴露給同包中的其他類訪問;protected很多時候出現在繼承關係中,父類只希望被子類訪問的字段和方法時;
繼承
從面向對象的角度上說,繼承是一種從一般到特殊的關係,是一種“is a”的關係,即子類是對父類的拓展,是一種特殊的父類,比如:狗是動物的一種特殊情況,狗屬於動物;在這個例子中,動物是父類,狗是子類,狗繼承了動物的特徵和行為,並在動物的特徵和行為的基礎之上拓展自己的特徵和行為,構成了狗這種特殊的動物。
所以可以基於父類並對其加以拓展,產生新的子類定義,這就是繼承;子類可以通過繼承獲得父類原有的字段和方法,也可以增加父類所沒有的字段和方法,更是可以覆寫父類中的允許被子類覆蓋的字段或者方法。
在Java中使用”extends”關鍵字來表示子類和父類之間的繼承關係;在Java中,類之間的繼承關係只允許單繼承,不允許多繼承,一個類只能有一個直接父類。
語法格式:
<code>public class 子類類名 extends 父類類名
{
\t\t編寫自己特有的狀態和行為
}/<code>
也就是說一個類只能有一個直接的父類,不能出現類A同時繼承於類B和類C。即便不允許多繼承,但是多重繼承是被允許的。以下是一個多重繼承的例子:
動物有胎生動物和卵生動物之分,胎生動物有老虎,老虎又分華南虎,東北虎,孟加拉虎等。在繼承體系中可以這樣來表示:
華南虎——》老虎——》胎生動物——》動物
最終,華南虎通過多重繼承獲得了老虎、胎生動物、動物的特徵和行為。
繼承的優點:
- 有效的解決了代碼的重用問題,使代碼拓展更加靈活;
- 通過從繼承關係,可以從始至終完整的體現出一個應用體系,邏輯更加清晰;
- 一般在開發中是先有多個自定義類,再從多個類中寫共同的代碼,抽象生成一個父類,然後子類都繼承於它。使用web框架開發時,也會更多的使用繼承來拓展框架的功能,以適應不同的業務需求。
子類可以繼承父類的哪些成員(根據訪問修飾符來判斷):
- 父類中使用protected、public修飾的成員;
- 父類和子類在同一個包中,父類中缺省修飾符的成員可以被子類繼承;
不能繼承的是:
- 父類中使用private修飾的成員;
- 父類的構造器,子類也不能繼承,因為構造器必須和當前的類名相同,父類的構造器是父類的名稱,子類的構造器是子類的名稱;
方法重寫和方法重載
方法重寫(Override) :從父類繼承的方法(行為)不符合子類的功能需求,那此時子類就需要重新實現父類的方法,並重寫方法體,以實現子類需求。
方法重寫的原則:一同兩小一大
一同:
- 方法簽名必須相同。 方法簽名= 方法名 + 方法的參數列表,參數類型、參數個數、參數順序都必須一致
兩小:
- 子類方法的返回值類型和父類方法的返回類型相同或者是其子類,子類可以返回一個更加具體的子類.
- 子類方法聲明拋出的異常類型和父類方法聲明拋出的異常類型相同或者是其子類,子類方法中聲明拋出的異常小於或等於父類方法聲明拋出異常類型;子類方法可以同時聲明拋出多個屬於父類方法聲明拋出異常類的子類(RuntimeException類型除外,RuntimeException是個特例);
<code>// 父類
class SuperClass {
protected void work() throws Exception {}
}
// 子類
class SubClass extends SuperClass {
@Override
protected void work() throws NullPointerException, NumberFormatException {}
}/<code>
一大:
- 子類方法的訪問權限與父類方法訪問權限相同或者更大;而private方法不能被子類所繼承,也就不會被覆蓋。
在重寫方法父類方法時,使用@Override註解修飾方法,若是重寫錯誤,就會報編譯錯誤,是一大開發利器;這裡需要注意的是隻有方法會被重寫,字段則沒有重寫一說。
方法重載(Overload): 在同一個類中,方法名字相同,但是因方法參數列表不同而又不同的實現,這樣的機制稱為方法重載,其實現原則是:兩同一不同 ,返回值並不計入其中。
兩同:相同類,方法名(只是方法名,不是方法簽名)相同
一不同:方法參數列表不同,包括參數類型、參數個數、參數順序都必須一致
<code>protected void work() {}
protected int work(String str) {
return 0;
}/<code>
方法重載(Overload)和方法重寫(Override)兩者只是名字上比較接近,其本身並沒有關係。
this 關鍵字
this關鍵字表示當前對象,當前對象就是this所在的這個對象。this主要存在於兩個位置:
- 構造器中: 就表示當前正在創建的對象
- 方法中: 表示方法的調用者對象
當一個對象創建之後,JVM會為對象分配一個引用該對象自身的引用:this。this的使用是為了:
- 解決成員變量和方法參數、局部變量之間的二義性,使用this可以顯式指向成員變量;
- 同類中實例方法間相互調用,雖然此時可以省略不寫,但還是建議不要省略,以提高代碼的可讀性。
- 將this作為參數傳遞給另一個方法;
- 將this作為方法的返回值(鏈式方法編程);
- 構造器重載的互相調用,this([參數])必須寫在構造方法第一行;
- static不能和this一起使用;因為當字節碼被加載進JVM的時候,static成員就已經存在了,但是此時對象很有可能還沒有被創建,沒有對象也就沒有this。
super 關鍵字
在對象中this表示當前對象,而super則表示當前對象的父類對象。在子類初始化過程中會創建子類對象,但在創建子類對象之前,會先創建父類對象;也就是說調用子類構造器之前,在子類構造器中會先調用父類的構造器,如果沒有顯式的調用父類構造器,那麼默認情況下會隱式的調用父類無參數構造器。
super關鍵字用於顯式調用父類方法、構造器和字段;可以使用super解決子類隱藏了父類的字段情況;在子類方法中,調用父類被覆蓋的方法;在子類構造器中,調用父類構造器。
<code>class SuperClass {
public SuperClass() {}
}
class SubClass extends SuperClass {
public SubClass() {
super(); // 調用父類構造器
}
}/<code>
父類構造器的不可或缺性:
- 如果父類不存在可以被子類訪問/調用的構造器,則子類就不可能存在。也就是必須要先有父類對象,而後才會有子類對象;
- 如果父類沒有提供無參數構造器,子類就必須顯式地通過super語句去調用父類帶參數的構造器。子類對象在初始化過程中,必須先調用父類構造器,而後再調用子類構造器。
繼承中的隱藏
上文中提到了隱藏的概念,繼承中的隱藏表示會忽略一些特徵和方法,比如靜態字段和靜態方法:
- 滿足繼承的訪問權限下,隱藏父類靜態方法:若子類定義的靜態方法的簽名和超類中的靜態方法簽名相同,那麼此時就是隱藏父類方法。注意:僅僅是在子類存在和父類一模一樣的靜態方法的情況下。
- 滿足繼承的訪問權限下,隱藏父類字段:若子類中定義的字段和父類中的字段名相同(忽略數據類型),此時是隱藏父類字段,但是可以通過super訪問被隱藏的字段。
- 隱藏本類字段:若本類中的局部變量名和字段名相同,此時就是隱藏本類字段,可以通過this訪問被隱藏的字段。
無論是this,還是super,都不能和static一起使用。
Object 類
在Java中除去Object類之外的每一個類都有一個直接或間接的父類:Object類。也就是說除去Object類之外的類都是Object類的直接子類或間接子類。
比如:
<code>class Person {}
class Student extends Person{}/<code>
此時Student類的直接父類是Person,Person類的直接父類是Object類,Object類是Student類的間接父類。
Object類是Java的基類,Java中的類都是Object的直接或者間接的子類,Object本身是指對象的意思, 它是所有的對象都具有的共同的行為的抽象類,其他類都會直接或者間接繼承於Object類,然後也就擁有了Object類中的方法。
<code>class SuperClass {}
等價於
class SuperClass extends Object {}/<code>
Object類的常見方法:
- finalize() :當垃圾回收器確定不存在對該對象的更多引用時,也就是垃圾回收器會在回收對象之前,會先調用該方法;該方法我們一般不會調用。
- getClass() :返回當前對象的真實類型。
- hashCode(): 返回該對象的哈希碼值,hashCode(哈希碼值)決定了對象再哈希表中的存儲位置,不同對象的存儲位置是不一樣的,所以hashCode也會是不一樣的。
- equals(Object obj) :用當前對象(this)和參數obj做比較,在Object類中的equals方法,比較對象的內存地址。官方建議:每個類都應該覆蓋equals方法,不要比較內存地址,而是去比較內容數據。
- toString():表示把一個對象轉換為字符串,打印對象時,調用的就是對象的toString方法,默認情況下打印的是對象的十六進制的hashCode值。 官方建議:應該每個類都應該覆蓋toString,返回我們真正關心的數據。
多態
通過上文,講清楚了繼承關係,繼承關係是一種”is a”(是一種)的關係,也就是說子類是父類的一種特殊情況;既然子類是一種特殊的父類,我們是否可以認為子類對象就是父類類型的對象。
考慮以下的代碼:
<code>Animal d = new Dog(); //創建一隻狗對象,類型是動物
Animal c = new Cat(); //創建一隻貓對象,類型是動物/<code>
這個時候,多態就產生了。我們以下面的代碼為例,詳細解釋什麼是多態:
<code>Animal a = new Dog();/<code>
在上例中,對象a具有兩種類型:
- 編譯類型: 聲明對象變量的類型,Animal;表示把對象看作是什麼類型。
- 運行類型: 對象的真實類型,Dog;運行類型--->對象的真實類型。
編譯類型必須是運行類型的父類或與之相同,當編譯類型和運行類型不同的時候,多態就產生了。所謂多態是指對象具有多種形態,對象可以存在不同的形式:
<code>Animal a = null;
a = new Dog(); //a此時表示Dog類型的形態
a = new Cat(); //a此時表示Cat類型的形態/<code>
多態可以是類和類之間的繼承關係,也可以是接口和實現類間的實現關係,一般情況下指的都是接口和實現類間的實現關係。
多態的特點:把子類對象賦給父類類型的變量,在運行時期會表現出具體的子類特徵,比如父類類型的變量調用子類的方法。
多態的好處:通過一個例子呈現
需求:給飼養員提供一個餵養動物的方法,用於餵養動物。
- 如果沒有多態,針對於不同類型的動物,得提供不同的餵養方法。可拓展性差,方法重用性低,不優雅。
- 存在多態:提供統一的餵養方法,大大減輕了飼養員的工作量。從上述例子,不難發現:當把不同的子類對象都當作父類類型來看待,可以屏蔽不同子類對象之間的實現差異,從而寫出通用的代碼達到通用編程,以適應需求的不斷變化。
數據類型轉換
基本數據類型轉換:大和小表示的是可存儲的容量範圍。
自動類型轉換:把小類型的數據賦給大類型的變量:
- byte b = 12; byte是1個字節
- int i = b; int是4個字節
強制類型轉換:把大類型的數據賦給小類型的變量。
- short s = (short) i ;short是2個字節
引用類型的轉換: 引用類型的大和小,指的是父類和子類的關係。
自動類型轉換 : 把子類對象賦給父類變量(多態)。
<code>Animal a = new Dog();
Object obj = new Dog();/<code>
強制類型轉換:把父類類型對象賦給子類類型變量(對象的真實類型是子類類型)。
<code>Animal a = new Dog();
Dog d = (Dog)a;/<code>
instanceof 運算符
instanceof 運算符: 判斷該對象是否是某一個類的實例。
語法格式:
<code>boolean b = 對象A instanceof 類B; // 判斷 A對象是否是 B類的實例,如果是,返回true./<code>
instanceof運算符:
- 若對象是類的實例返回true。
- 若對象是類的父類的實例也返回true。
組合關係(has a)
在繼承關係中,子類可以繼承到父類中部分的成員,那麼此時子類是可以修改到父類的信息的,此時的繼承關係破壞了封裝,讓子類擁有了本不該具有的功能。那麼這時可以使用”包含關係”(has a)的組合關係。
可以這麼理解組合關係:把另一個類當作屬性來獲取其特徵和行為。
比如:需求是:我想擁有天子的權力;
- 方式1: 當太子,此時表現的是繼承關係;
- 方式2: 學曹操挾天子以令諸侯,挾持天子,此時是組合/包含關係。
思考:如果A類想要得到B的功能行為,如若A類是B類的一種特殊情況,就應該採用繼承來實現,否則使用組合方式。
<code>class Cat {
public void eatMouse() {
\tSystem.out.println("貓吃老鼠");
}
\t
}
class XiaoMing {
\t// 把cat類當作屬性引入
private Cat cat;
public XiaoMing() {
\tcat = new Cat();
}
\tpublic void killMouse() {
\t\tcat.eatMouse():
}
}/<code>
完結。
閱讀更多 老夫不正經 的文章