面向對象簡介
何爲“面向對象”的編程思想?
1.何爲“思想”和“編程思想”?
先問你個問題:你想做個怎樣的人?
可能你會回答:我想做個好人, 孝敬父母, 尊重長輩, 關愛親朋……你看, 這就是思想。 這是你做人的思想, 或者說, 是你做人的原則。做人有做人的原則, 編程也有編程的原則。 這些編程的原則呢, 就是編程思想。
2.面向過程(POP) 與 面向對象(OOP)
二者都是一種思想,面向對象是相對於面向過程而言的。
面向過程, 強調的是功能行爲,以函數爲最小單位,考慮怎麼做。
面向對象,將功能封裝進對象, 強調具備了功能的對象,以類/對象爲最小單位,考慮誰來做。面向對象更加強調運用人類在日常的思維邏輯中採用的思想方法與原則,如抽象、分類、繼承、聚合、多態等。
面向對象: Object Oriented Programming
面向過程: Procedure Oriented Programming
3.面向對象與面向過程的實例
面向過程
1.打開冰箱
2.把大象裝進冰箱
3.把冰箱門關住
面向對象
人{
打開(冰箱) {
冰箱.開門();
}
操作(大象){
大象.進入(冰箱);
}
關閉(冰箱){
冰箱.關門();
}
}
冰箱{
開門(){ }
關門(){ }
}
大象{
進入(冰箱){ }
}
面向對象的思想概述
- 程序員從面向過程的執行者轉化成了面向對象的指揮者
- 面向對象分析方法分析問題的思路和步驟:
- 根據問題需要,選擇問題所針對的現實世界中的實體。
- 從實體中尋找解決問題相關的屬性和功能,這些屬性和功能就形成了概念世界中的類。
- 把抽象的實體用計算機語言進行描述, 形成計算機世界中類的定義。即藉助某種程序語言,把類構造成計算機能夠識別和處理的數據結構。
- 將類實例化成計算機世界中的對象。對象是計算機世界中解決問題的最終工具。
面向對象的兩個要素
- 面向對象程序設計的重點是類的設計
- 設計類,就是設計類的成員。
面向對象的三大特徵
封裝性
問題引入
當我們創建一個類的對象以後,我們可以通過"對象.屬性"的方式,對對象的屬性進行賦值。這裏,賦值操作要受到屬性的數據類型和存儲範圍的制約。除此之外,沒有其他制約條件。但是,在實際問題中,我們往往需要給屬性賦值加入額外的限制條件。這個條件就不能在屬性聲明時體現,我們只能通過方法進行限制條件的添加。(比如:setLegs())同時,我們需要避免用戶再使用"對象.屬性"的方式對屬性進行賦值。則需要將屬性聲明爲私有的(private).
–>此時,針對於屬性就體現了封裝性。
封裝性的思想
爲什麼需要封裝?封裝的作用和含義?
我要用洗衣機,只需要按一下開關和洗滌模式就可以了。有必要了解洗衣機內部的結構嗎?有必要碰電動機嗎?
我要開車, …
使用者對類內部定義的屬性(對象的成員變量)的直接操作會導致數據的錯誤、混亂或安全性問題。所以應該使用封裝,將信息隱藏.
我們程序設計追求“高內聚,低耦合”。
高內聚 :類的內部數據操作細節自己完成,不允許外部干涉;
低耦合 : 僅對外暴露少量的方法用於使用。
封裝性的設計思想
隱藏對象內部的複雜性,只對外公開簡單的接口。便於外界調用,從而提高系統的可擴展性、可維護性。通俗的說, 把該隱藏的隱藏起來,該暴露的暴露出來。 這就是封裝性的設計思想。
封裝性的體現
Java中通過將數據聲明爲私有的(private
), 再提供公共的(public)方法:getXxx()
和setXxx()
實現對該屬性的操作, 以實現下述目的:
- 隱藏一個類中不需要對外提供的實現細節;
- 使用者只能通過事先定製好的方法來訪問數據, 可以方便地加入控制邏輯,
限制對屬性的不合理操作; - 便於修改, 增強代碼的可維護性;
封裝性的體現,需要權限修飾符來配合。
四種訪問權限修飾符
Java權限修飾符public、 protected、 (缺省)、 private置於類的成員定義前,用來限定對象對該類成員的訪問權限。
-
Java規定的4種權限(從小到大排列):private、缺省、protected 、public
-
4種權限可以用來修飾類及類的內部結構:屬性、方法、構造器、內部類
-
具體的,4種權限都可以用來修飾類的內部結構:屬性、方法、構造器、內部類
-
修飾類的話,只能使用:缺省、public
修飾符 | 類內部 | 同一個包 | 不同包的子類 | 同一個工程 |
---|---|---|---|---|
private | yes | |||
default(缺省) | yes | yes | ||
protected | yes | yes | yes | |
public | yes | yes | yes | yes |
注意:
對於class的權限修飾只可以用public和default(缺省)。
- public類可以在任意地方被訪問。
- default類只可以被同一個包內部的類訪問。
總結封裝性
Java提供了4種權限修飾符來修飾類及類的內部結構,體現類及類的內部結構在被調用時的可見性的大小。
繼承性
爲什麼要有繼承?
多個類中存在相同屬性和行爲時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行爲,只要繼承那個類即可。
此處的多個類稱爲子類(派生類), 單獨的這個類稱爲父類(基類或超類)。 可以理解爲:“子類 is a 父類”
語法規則
class Subclass extends SuperClass{ }
作用
-
繼承的出現減少了代碼冗餘,提高了代碼的複用性。
-
繼承的出現,更有利於功能的擴展。
-
繼承的出現讓類與類之間產生了關係,提供了多態的前提。
繼承的體現
-
一旦子類A繼承父類B以後,子類A中就獲取了父類B中聲明的所有的屬性和方法。特別的,父類中聲明爲private的屬性或方法,子類繼承父類以後,仍然認爲獲取了父類中私有的結構。只有因爲封裝性的影響,使得子類不能直接調用父類的結構而已。
-
子類繼承父類以後,還可以聲明自己特有的屬性或方法:實現功能的拓展。子類和父類的關係,不同於子集和集合的關係。
繼承的規則
-
一個類可以被多個子類繼承。
-
Java中類的單繼承性:一個類只能有一個父類
-
子父類是相對的概念。
-
子類直接繼承的父類,稱爲:直接父類。間接繼承的父類稱爲:間接父類
-
子類繼承父類以後,就獲取了直接父類以及所有間接父類中聲明的屬性和方法
-
子類不能直接訪問父類中私有的(private)的成員變量和方法。
-
Java只支持單繼承和多層繼承, 不允許多重繼承
-
一個子類只能有一個父類
-
一個父類可以派生出多個子類
class SubDemo extends Demo{ } //ok class SubDemo extends Demo1,Demo2...//error
示例
注意事項
-
不 要僅爲了獲取其他類中某個功能而去繼承
-
在Java 中,繼承的關鍵字用的是“extends”,即子類不是父類的子集,而是對父類的“擴展” 。
-
如果我們沒有顯式的聲明一個類的父類的話,則此類繼承於java.lang.Object類
-
所有的java類(除java.lang.Object類之外)都直接或間接的繼承於java.lang.Object類。意味着,所有的java類具有java.lang.Object類聲明的功能。
子類對象實例化的全過程
- 從結果上來看:(繼承性)
-
子類繼承父類以後,就獲取了父類中聲明的屬性或方法。
-
創建子類的對象,在堆空間中,就會加載所有父類中聲明的屬性。
- 從過程上來看:
-
當我們通過子類的構造器創建子類對象時,我們一定會直接或間接的調用其父類的構造器,進而調用父類的父類的構造器,…
-
直到調用了java.lang.Object類中空參的構造器爲止。
-
正因爲加載過所有的父類的結構,所以纔可以看到內存中有父類中的結構,子類對象纔可以考慮進行調用。
注意:
雖然創建子類對象時,調用了父類的構造器,但是自始至終就創建過一個對象,即爲new的子類對象。
執行過程圖示
代碼
class Creature {
public Creature() {
System.out.println("Creature無參數的構造器");
}
}
class Animal extends Creature {
public Animal(String name) {
System.out.println("Animal帶一個參數的構造器,該動物的name爲" + name);
}
public Animal(String name, int age) {
this(name);
System.out.println("Animal帶兩個參數的構造器,其age爲" + age);
}
}
public class Wolf extends Animal {
public Wolf() {
super("灰太狼", 3);
System.out.println("Wolf無參數的構造器");
}
public static void main(String[] args) {
new Wolf();
}
}
思考
1.爲什麼super(…)和this(…)調用語句不能同時在一個構造器中出現?
2.爲什麼super(…)或this(…)調用語句只能作爲構造器中的第一句出現?
繼承成員變量和繼承方法的區別
子類繼承父類:
- 若子類重寫了父類方法,就意味着子類裏定義的方法徹底覆蓋了父類裏的同名方法,系統將不可能把父類裏的方法轉移到子類中。
- 對於實例變量則不存在這樣的現象,即使子類裏定義了與父類完全相同的實例變量,這個實例變量依然不可能覆蓋父類中定義的實例變量
多態性
如何理解多態性?
可以理解爲一個事物的多種形態。
何爲多態性?
對象的多態性:父類的引用指向子類的對象(或子類的對象賦給父類的引用)
- 可以直接應用在抽象類和接口上
多態性的作用
提高了代碼的通用性,常稱作接口重用
多態性的使用
虛擬方法調用
有了對象的多態性以後,我們在編譯期,只能調用父類中聲明的方法,但在運行期,我們實際執行的是子類重寫父類的方法。
總結:編譯,看左邊;運行,看右邊。
多態性的注意事項
Java引用變量有兩個類型: 編譯時類型和運行時類型。 編譯時類型由聲明該變量時使用的類型決定, 運行時類型由實際賦給該變量的對象決定。 簡稱: 編譯時, 看左邊;運行時, 看右邊。
-
若編譯時類型和運行時類型不一致, 就出現了對象的多態性(Polymorphism)
-
多態情況下, “看左邊” : 看的是父類的引用(父類中不具備子類特有的方法);“看右邊” : 看的是子類的對象(實際運行的是子類重寫父類的方法)
-
對象的多態 —在Java中,子類的對象可以替代父類的對象使用
-
一個變量只能有一種確定的數據類型
-
一個引用類型變量可能指向(引用)多種不同類型的對象
Person p = new Student(); Object o = new Person();//Object類型的變量o, 指向Person類型的對象 o = new Student(); //Object類型的變量o, 指向Student類型的對象
-
-
子類可看做是特殊的父類, 所以父類類型的引用可以指向子類的對象:向上轉型(upcasting)。
-
一個引用類型變量如果聲明爲父類的類型,但實際引用的是子類對象,那麼該變量就不能再訪問子類中添加的屬性和方法
Student m = new Student(); m.school = “pku”; //合法,Student類有school成員變量 Person e = new Student(); e.school = “pku”; //非法,Person類沒有school成員變量
屬性是在編譯時確定的,編譯時e爲Person類型,沒有school成員變量,因而編譯錯誤。
-
對象的多態性,只適用於方法,不適用於屬性(編譯和運行都看左邊)
多態性的使用前提
① 類的繼承或者實現關係 ② 方法的重寫 ③向上轉型
多態性示例
方法聲明的形參類型爲父類類型,可以使用子類的對象作爲實參調用該方法
public class Test {
public void method(Person e) {
// ……
e.getInfo();
}
public static void main(Stirng args[]) {
Test t = new Test();
Student m = new Student();
t.method(m); // 子類的對象m傳送給父類類型的參數e
}
}
虛擬方法調用(Virtual Method Invocation)
正常的方法調用
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
虛擬方法調用(多態情況下)
子類中定義了與父類同名同參數的方法,在多態情況下,將此時父類的方法稱爲虛擬方法,父類根據賦給它的不同子類對象,動態調用屬於子類的該方法。這樣的方法調用在編譯期是無法確定的。
Person e = new Student();
e.getInfo(); //調用Student類的getInfo()方法
編譯時類型和運行時類型
編譯時e爲Person類型,而方法的調用是在運行時確定的,所以調用的是Student類的getInfo()方法。 ——動態綁定
圖示
前提
Person類中定義了welcome()方法,各個子類重寫了welcome()。
執行:
多態的情況下,調用對象的welcome()方法,實際執行的是子類重寫的方法。
方法的重載與重寫與多態性(方法的重載是多態性的一種體現?NO)*
從編譯和運行的角度看:
重載,是指允許存在多個同名方法,而這些方法的參數不同。 編譯器根據方法不同的參數表, 對同名方法的名稱做修飾。對於編譯器而言,這些同名方法就成了不同的方法
。 它們的調用地址在編譯期就綁定了
。 Java的重載是可以包括父類和子類的,即子類可以重載父類的同名不同參數的方法
。
所以: 對於重載而言,在方法調用之前,編譯器就已經確定了所要調用的方法
,這稱爲“早綁定”或“靜態綁定” ;
而對於多態,只有等到方法調用的那一刻, 解釋運行器纔會確定所要調用的具體方法,這稱爲“晚綁定”或“動態綁定”
。
引用一句Bruce Eckel的話: “不要犯傻,如果它不是晚綁定, 它就不是多態。
”
所以說,方法的重載並不是多態性的一種體現
。
成員變量、成員方法與多態性
成員方法:
- 編譯時:要查看引用變量所聲明的類中是否有所調用的方法。
- 運行時: 調用實際new的對象所屬的類中的重寫方法。
成員變量:
- 不具備多態性,只看引用變量所聲明的類。
多態是編譯時行爲還是運行時行爲?
多態是運行時行爲
。
證明:
class Animal {
protected void eat() {
System.out.println("animal eat food");
}
}
class Cat extends Animal {
protected void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog eat bone");
}
}
class Sheep extends Animal {
public void eat() {
System.out.println("Sheep eat grass");
}
}
public class InterviewTest {
public static Animal getInstance(int key) {
switch (key) {
case 0:
return new Cat ();
case 1:
return new Dog ();
default:
return new Sheep ();
}
}
public static void main(String[] args) {
int key = new Random().nextInt(3);
System.out.println(key);
Animal animal = getInstance(key);
animal.eat();
}
}
多態性常遇見的問題
編譯時通過,運行時不通過
- p3的引用類型是子類
Woman
,不能強制轉換爲Man
Person p3 = new Woman();
Man m3 = (Man)p3;
- p4的引用類型是Person,不能強制轉換爲
Man
Person p4 = new Person();
Man m4 = (Man)p4;
編譯不通過
不能將父類的引用賦給子類,否則編譯不能通過
Man m5 = new Woman();
編譯通過,運行時也通過
可以將子類的引用賦給更高層次的父類,在強制向下轉型,則可以成功
Object obj = new Woman();
Person p = (Person)obj;