Java 基礎 —— Objects and Classes
OOP
面向物件程式設計(Object-Oriented Programming,OOP)
Java的程式設計單位是類,物件通過類進行例項化(“建立”)
三個特性:
- 封裝 Encapsulation (C++在類外可以定義函式)
- 繼承 Inheritance
- 多型 Polymorphism
抽象資料型別
類就是模版
Java中用class表示一個類,類是一個抽象的資料型別 (C++中用struct表示一個類)
類和物件
面向物件的開發方法把軟體系統看成各種物件的集合,物件就是最小的子系統,一組相關的物件能夠組合成更復雜的子系統。
物件是對問題領域中事件的抽象。物件具有以下特性:
- 萬物皆為物件 (問題領域中的實體和概念都可以抽象為物件。)
- 每個物件都是惟一的
- 物件具有屬性和行為
- 物件具有狀態 (狀態是指某個瞬間物件的各個屬性的取值。)
- 每個物件都是某個類的例項
類是具有相同屬性和行為的物件的集合。
- 同一個類的所有例項都有相同屬性,但屬性取值不一定相同,事實上它們的狀態不一定相同。
- 同一個類的所有例項都有相同行為,意味著它們具有一些相同的功能。
類是一組具有相同屬性和行為物件的模板。面向物件程式設計的主要任務就是定義物件模型中的各個類。
- 類是一種型別:是引用型別
- 類是元資料:描述資料的資料
(因為資料在面向物件領域裡以物件的形式存在,所以類是物件共有屬性和方法的抽象描述。)
Java程式是各種物件相互互動作用、而不是類。
在java中,類的宣告和實現在同一時間,而且必須在一起.
在C++中,類的宣告和實現可以被分開.
類中方法的定義
修飾符 返回型別 方法名(引數列表)異常丟擲型別 { …… }
- 必須有返回值,如果方法沒有返回值,必須用void宣告返回型別。
- 構造器沒有返回型別,構造器加上了返回型別就變成了一個普通方法的聲明瞭。
- 方法的修飾符可以同時有多個(多個修飾符之間沒有先後順序)
return_type:
如果方法定義了返回型別,那麼在方法體裡面就必須出現return語句,而且返回的資料型別要和宣告一致;
如果沒有返回型別的話(void),那麼方法體裡面可以寫return,也可以不寫return。當代碼執行了return語句,就不往下執行了,直接退出這個方法並返回。
方法中定義的引數通常叫做形參,呼叫有引數的方法時,通常會傳遞一些實參給方法
引數傳遞
- 值傳遞: 對於基本資料型別,引數通過值傳遞。 (把實參的值複製一份再傳給形參)
- 引用傳遞: 對於引用型別,引數通過引用(物件的引用)傳遞。 (把實參引用中地址值複製一份再傳給形參)
this關鍵字
在方法呼叫、引數傳遞過程中,極有可能出現引數名稱與例項變數名同名的情況。
在一個方法內,可以定義和成員變數同名的區域性變數或引數,此時成員變數被遮蔽。
作用:
1. 區別成員變數和區域性變數
2. 在類中可以表示當前類的物件
3. 在類的構造器中用this關鍵字可以呼叫類的其他的構造器
注意:
this只能出現在建構函式程式碼塊中的第一句(前提是this這個時候代表的是呼叫其他構造器)
由this呼叫的構造器不會再建立新的物件
例如:
public class Car{
private String name;
private double price;
public Car(){
//無參構造器中呼叫了有一個引數的構造器
this("Tom");
}
public Car(String name){
//有一個引數的構造器中呼叫了有兩個引數的構造器
this(name,100000);
}
public Car(String name,double price){
this.name = name;
this.price = price;
}
}
資料隱藏
這裡的資料是指類中的屬性
如何隱藏?
在封裝的屬性前面用private修飾,表示該屬性不能被其它類訪問和修改,只能被本類訪問和修改,範圍限制在本類內。
Java中封裝有兩個方面:
- 屬性的封裝
- 方法的封裝
封裝
對屬性的封裝
可以修飾屬性的修飾符:
private
public
protected
“default”
(這四種修飾符可以修飾成員變數,也可以修飾方法)
在屬性(例項變數)前加private, 然後通過統一的方法訪問以及修改這些屬性值;
使用get/set方法來訪問類中被private修飾的屬性。方法的封裝:(用方法來封裝程式碼)
使用者的角度:使用者只關心方法的使用,不管裡面到底怎麼實現的細節.
程式設計的角度 :為了程式碼的重用.
方法過載(overload)
類的同一種功能有多種實現方式(方法名相同,引數不同)
一個方法是另一個方法的過載方法。
過載必須滿足以下條件:
1) 方法名稱相同。
2) 引數列表不同(引數型別、個數和順序)。
注意: 返回型別可以不相同。
在一個類中不允許定義兩個方法名相同,並且引數簽名也完全相同的方法。
因為假如存在這樣的兩個方法,Java虛擬機器在執行時就無法決定到底執行哪個方法。引數簽名是指引數的型別、個數和順序。
建立和初始化物件
定義類的形式、定義方法的形式,構建好類之後,程式要真實的執行,還得通過物件的互動來完成。
建立好了類,只是建立了構建物件的模板。
可以通過new操作符,快速地構建出物件。
使用new有以下作用(有以下幾步):
1.為物件分配記憶體空間,將物件的例項變數自動初始化預設值;
2.如例項變數顯式初始化,將初始化值賦給例項變數(把預設值覆蓋掉);
3.呼叫構造方法;
4.返回物件的地址值;
(步驟細分。。。)
構造方法
(構造器/建構函式/構造方法)
定義:
- 構造方法的名字和類的名字相同
- 沒有返回型別,有返回型別的構造器就變成了普通方法。
呼叫時刻:
在建立物件的時候呼叫;
- 注意:
是先建立物件,然後初始化物件中的屬性值,最後在呼叫構造器。
- 注意:
- 作用:
在建立物件的時候做一些物件中資料的初始化工作。(因為在new的後面就是寫的構造器)
- 構造方法的呼叫:
* 當前類的其他構造方法通過this語句呼叫它;
* 當前類的子類的構造方法通過super語句呼叫它;
* 在程式中通過new語句呼叫它;
構造方法過載
通過過載構造方法來表達物件的多種初始化行為。在一個類的多個構造方法中,可能會出現一些重複操作。為了提高程式碼的可重用性,Java語言允許在一個構造方法中,用this語句來呼叫另一個構造方法。
1.假如在一個構造方法中使用了this語句,那麼它必須作為構造方法的第一條要執行的語句(不考慮註釋語句)。
2.只能在一個構造方法中用this語句來呼叫類的其他構造方法,而不能在例項方法中用this語句來呼叫類的其他構造方法;
3.只能用this語句來呼叫其他構造方法,而不能通過方法名來直接呼叫構造方法。
預設的構造方法
構造方法可分為兩種:
1) 隱含的預設構造方法(無參構造器);
2) 程式顯示定義的構造方法;
在Java語言中,每個類至少有一個構造方法。
為了保證這一點,如果使用者定義類中沒有提供任何構造方法,那麼在執行的時候JVM將自動提供一個隱含的預設構造方法。
該構造方法沒有引數,用public修飾,而且方法體為空,格式如下:
public ClassName(){} //隱含的預設構造方法
在程式中也可以顯示地定義預設構造方法。
如果類中顯式定義了一個或多個構造方法,那麼Java語言便不再分配隱含的預設構造方法。
子類
什麼是繼承?
父子類間的繼承關係也叫“is a”關係。這種關係通過類宣告上的extends關鍵字型現。
一個子類只有一個父類,一個父類可有多個子類。
為什麼要繼承?
- 站在巨人的肩膀上;通過繼承,我們可以快速構建出一個帶有豐富功能的新類;
- 不修改原始碼,修改既有類的行為;通過繼承,在子類中構建父類中一樣的方法,可以改變父類方法的行為。
Object類簡略介紹
所有的Java類都直接或間接地繼承了java.lang.Object類。
Object類是所有Java類的祖先,在這個類中定義了所有的Java物件都具有相同行為。
繼承
子類繼承了父類的屬性和方法:
1) 父子類同包,子類繼承父類中public、protected和預設訪問級別的成員變數和成員方法;
2) 父子類不同包,子類繼承父類中public、protected的成員變數和成員方法;
細節:
1、構造器不能被繼承
2、方法和例項變數可以被繼承
3、子類構造器隱式地呼叫父類的預設無參構造器;
4、如果父類中沒有定義無參構造器,只定義了有參構造器,
那麼子類構造器則必須顯式地呼叫父類的有參構造器(通過super(…)),且必須放置在第一條語句,否則會有語法錯誤。
5、this()和super()在構造器中都必須為第一條語句,兩者不能同時出現。
6、當一個子類繼承了一個父類後,父類中所有的欄位和方法都被子類繼承擁有,子類可以任意的支配使用,每個子類物件中都擁有了父類中的所有欄位。
當構造一個子類的例項物件時,該物件的例項變數包括了子類本身以及父類中的所有例項變數,例項方法也包括了子類和父類中的所有例項方法。
7、子類構造器用來初始化子類中所有的例項變數,而父類構造器super(實參)用來初始化父類中所有的例項變數。
所以在堆中為子類例項物件分配的記憶體區域中包括了子類和父類中所有初始化後的例項變數
方法覆蓋(重寫)
1. 方法覆蓋只存在於子類和父類(包括直接父類和間接父類)之間。在同一個類中方法只能被過載,不能被覆蓋;
2. 靜態方法:
不能覆蓋;
a. 父類的靜態方法不能被子類覆蓋為非靜態方法 //編譯出錯
b. 子類可以定義與父類的靜態方法同名的靜態方法 (但是這個不是覆蓋)
c. 父類的非靜態方法不能被子類覆蓋為靜態方法 //編譯出錯
3. 私有方法:
不能被子類覆蓋
4. 抽象方法:(之後會詳細討論抽象方法)
可以覆蓋:
a. 父類的抽象方法可以被子類覆蓋為非抽象方法: 子類實現父類抽象方法;
b. 父類的抽象方法可以被子類覆蓋為抽象方法: 重新宣告父類的抽象方法;
c. 父類的非抽象方法可以被子類覆蓋為抽象方法;
super關鍵字
1. 為什麼要使用super關鍵字?
1) 子類中要訪問父類方法或變數。
2) 子類中呼叫父類的構造器
2. 使用注意事項:
a. 只能在構造方法或例項方法內使用super關鍵字,在靜態方法和靜態程式碼塊內不能使用super關鍵字。
b. 在子類構造方法中如沒有使用super關鍵字,會隱式呼叫父類的無參構造方法;
c. 構造方法中this(...)和super(...)不能同時出現;
多型
多型的兩種描述:
1.一個父類型別的引用可以指向他任何一個子類的物件
2.[相同]類域的[不同]物件執行[同一]方法的時候會有[不同]的表現多型是出現在具有繼承關係的兩個類的物件之間,所以它不像方法過載(發生在一個類中)在編譯期間發生(也就是確定下來),而是在執行期間發生(確定下來)。
Java中的方法是在程式碼執行時動態繫結的。
型別轉換
轉換:
1) 先使用instanceof 識別型別
2) 子型別隱式地擴充套件到父型別(自動轉換)
3) 父型別必須顯式地縮小到子型別
轉換規則:被轉換的實際物件型別一定是轉換以後物件型別的自身或者子類。
Person p = new Person();
Student s = (Student)p;// 編譯不會錯,執行時錯誤
Person p2 = new Student();
Student s = (Student)p2 或者 Person p = (Student)p2;// 正確
繼承現象總結:
1. 子類重寫父類方法,呼叫子類方法;
2. 子類屬性與父類同名(不管子類屬性前修飾符如何均允許),如獲取屬性,看獲取屬性方法位置,
如在父類中,獲取的是父類屬性,如在子類中,獲取的是子類屬性;
3. 子類私有方法與父類私有方法同名,如呼叫該方法,看私有方法被呼叫的位置,
如在父類中,呼叫的是父類方法,如在子類中,呼叫的是子類方法;
4. 子類靜態方法與父類靜態方法同名,子類靜態方法遮蔽父類靜態方法。如呼叫該靜態方法,看例項化物件時所宣告的型別,
如宣告為父類,呼叫的是父類中靜態方法,反之是子類中靜態方法。