一、Java程式設計思想之對象入門
- 前言
- 1、面向對象程式設計
- 2、接口的由來----接收請求
- 3、public,private,protected的由來----實作方案的隐藏
- 4、成員對象的由來----方案的重複使用
- 5、繼承的由來----重新使用接口
-
- 5.1、繼承特點:
- 5.2、繼承關鍵字
-
- 5.2.1、extends與 implements關鍵字
- 5.2.2、super 與 this 關鍵字
- 5.3.3、final關鍵字
- 5.3、重寫和重載
-
- 5.4、上溯造型和下塑造型
- 6、抽象類的由來----限制基礎類隻為自己的衍生類提供一個接口,不允許建立一個自己的執行個體
- 7、對象的建立和存在時間----對象需要的資料位于哪兒,如何控制對象的“存在時間”呢?
-
-
- 7.1集合與繼承器----不知道需要對象個數、存活時間,如何儲存呢?
-
- 最後
前言
重寫梳理一下Java基礎知識,結合着《Java程式設計思想》,思考為什麼要這樣?
1、面向對象程式設計
(1) 所有東西都是對象。可将對象想象成一種新型變量;它儲存着資料,但可要求它對自身進行操作。
(2) 程式是一大堆對象的組合;通過消息傳遞,各對象知道自己該做些什麼。
(3) 每個對象都有自己的存儲空間,可容納其他對象。或者說,通過封裝現有對象,可制作出新型對象。
(4) 每個對象都有一種類型。一個類最重要的特征就是“能将什麼消息發給它?”。
(5) 同一類所有對象都能接收相同的消息。
2、接口的由來----接收請求
如何利用對象完成真正有用的工作呢?必須有一種辦法能向對象送出請求,令其做一些實際的事情,比如完成一次交易、在螢幕上畫一些東西或者打開一個開關等等。
每個對象僅能接受特定的請求。我們向對象發出的請求是通過它的“接口”(Interface)定義的,對象的“類型”或“類”則規定了它的接口形式。
3、public,private,protected的由來----實作方案的隐藏
從根本上說,大緻有兩方面的人員涉足面向對象的程式設計:“類建立者”(建立新資料類型的人)以及“客戶程式員”。對類建立者來說,他們的目标則是從頭建構一個類,隻向客戶程式員開放有必要開放的東西(接口),其他所有細節都隐藏起來。是以便有了public,private,protected 以及暗示性的friendly。
4、成員對象的由來----方案的重複使用
新類可由任意數量和類型的其他對象構成。這個概念叫作“組織”——在現有類的基礎上組織一個新類。對象的組織具有極大的靈活性。新類的“成員對象”通常設為“私有”(Private),使用這個類的客戶程式員不能通路它們。這樣一來,我們可在不幹擾客戶代碼的前提下,從容地修改那些成員。也可以在“運作期”更改成員,這進一步增大了靈活性。
後面要講到的“繼承”并不具備這種靈活性。是以建立類的時候,首先應考慮“組織”對象;這樣做顯得更加簡單和靈活。
5、繼承的由來----重新使用接口
多種資料類型,令其實作大緻相同的功能,代碼量大且臃腫。但若能利用現成的資料類型,對其進行“克隆”,再根據情況進行添加和修改就好了。“繼承”正是針對這個目标而設計的。
公共父類:
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
企鵝類:
public class Penguin extends Animal {
public Penguin(String myName, int myid) {
super(myName, myid);
}
}
老鼠類:
public class Mouse extends Animal {
public Mouse(String myName, int myid) {
super(myName, myid);
}
}
5.1、繼承特點:
(1)子類擁有父類非 private 的屬性、方法。
(2)子類可以擁有自己的屬性和方法,即子類可以對父類進行擴充。
(3)子類可以用自己的方式實作父類的方法。
(4)類的繼承是單一繼承。
(5)提高了類之間的耦合性(繼承的缺點,耦合度高就會造成代碼之間的聯系越緊密,代碼獨立性越差)
5.2、繼承關鍵字
5.2.1、extends與 implements關鍵字
extends 隻能繼承一個類,類的繼承是單一繼承。
implements 關鍵字可以變相的使java具有多繼承的特性,使用範圍為類繼承接口的情況,可以同時繼承多個接口。
5.2.2、super 與 this 關鍵字
super關鍵字:我們可以通過super關鍵字來實作對父類成員的通路,用來引用目前對象的父類。
this關鍵字:指向自己的引用。
5.3.3、final關鍵字
final 關鍵字聲明類可以把類定義為不能繼承的,即最終類;或者用于修飾方法,該方法不能被子類重寫。
注:執行個體變量也可以被定義為 final,被定義為 final 的變量不能被修改。被聲明為 final 類的方法自動地聲明為 final,但是執行個體變量并不是 final。
5.3、重寫和重載
5.4、上溯造型和下塑造型
方法的重寫、重載與動态連接配接構成多态性。Java之是以引入多态的概念,原因之一是它在類的繼承問題上和C++不同,後者允許多繼承,這确實給其帶來的非常強大的功能,但是複雜的繼承關系也給C++開發者帶來了更大的麻煩,為了規避風險,Java隻允許單繼承。
我定義了一個子類Cat,它繼承了Animal類。我可以通過 Cat c = new Cat(); 執行個體化一個Cat的對象。但當我這樣定義時: Animal a = new Cat(); 這代表什麼意思呢?
它表示我定義了一個Animal類型的引用,指向建立的Cat類型的對象。那麼這樣做有什麼意義呢?
因為子類是對父類的一個改進和擴充,是以一般子類在功能上較父類更強大, 定義一個父類類型的引用指向一個子類的對象既可以使用子類強大的功能,又可以抽取父類的共性。
是以,父類類型的引用可以調用父類中定義的所有屬性和方法,而對于子類中定義而父類中沒有的方法,它是無可奈何的; 對于父類中定義的方法,如果子類中重寫了該方法,那麼父類類型的引用将會調用子類中的這個方法,這就是動态連接配接。
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 對象調用 show 方法
show(new Dog()); // 以 Dog 對象調用 show 方法
Animal a = new Cat(); // 向上轉型
a.eat(); // 調用的是 Cat 的 eat
Cat c = (Cat)a; // 向下轉型
c.work(); // 調用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 類型判斷
if (a instanceof Cat) { // 貓做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃魚");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨頭");
}
public void work() {
System.out.println("看家");
}
}
程式輸出:
吃魚
抓老鼠
吃骨頭
看家
吃魚
抓老鼠
下面我們看另外一個例子:
/**
* @author WangQun
* 動物抽象類
*/
abstract class Animal {
public abstract void speak();
public void eat(){
// 悶頭吃,不做額外的事情
}
}
/**
* @author WangQun
* 門神接口
*/
interface DoorGod {
void guard();
}
/**
* @author WangQun
* 貓,繼承自動物
*/
class Cat extends Animal {
@Override
public void eat() {
try {
Thread.sleep( 1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
super .eat();
}
@Override
public void speak() {
System.out.println( " 喵喵 " );
}
}
/**
* @author WangQun
* 狗,繼承自動物,實作門神接口
*/
class Dog extends Animal implements DoorGod{
@Override
public void speak() {
System.out.println( " 汪汪 " );
}
public void guard() {
while ( true ){
System.out.println( " 汪汪 " );
}
}
}
其中Animal為基類,定義speak和eat方法,eat方法給出了空實作; DoorGod為門神接口,定義了 guard方法來守護家門; Cat為繼承Animal的子類,這裡假定貓有挑食的習慣,在eat中要耽擱點時間看看夥食;Dog也為繼承Animal的子類,同時它實作了DoorGod接口來守護家門。
先說說上溯造型(upcasting)。這個術語緣于繼承關系圖的傳統畫法:将基類至于頂部,而向下發展的就是派生類。根據上面的sample,我給出下面的一個小應用:
public class Main {
/**
* @param animal
* 上溯,傳入的參數強制轉換成其父類
*/
public static void upcasting(Animal animal){
animal.speak();
animal.eat();
}
public static void main(String[] args) {
Animal dog1 = new Dog();
upcasting(dog1);
Dog dog2 = new Dog();
upcasting(dog2);
}
}
由于upcasting(Animal animal)方法的參數是 Animal類型的,是以如果傳入的參數是 Animal的子類,傳入的參數就會被轉換成父類Animal類型,這樣你建立的Dog對象能使用的方法隻是Animal中的簽名方法;也就是說,在上溯的過程中,Dog的接口變窄了,它本身的一些方法(例如實作了 DoorGod的guard方法)就不可見了。如果你想使用Dog中存在而Animal中不存在的方法(比如guard方法),編譯時不能通過的。由此可見,上溯造型是安全的類型轉換。另一方面,雖然upcasting(Animal animal)方法的參數是 Animal類型,但傳入的參數可以是Animal的派生類(這也是OO程式設計中慣用的程式設計方法),這裡面就有個對象的類型識别問題,也就是運作時類型識别(run-time type identification,縮寫為RTTI) ,這也可以單獨寫一篇文章了,《Thinking in Java》中的第10章詳細地闡述了RTTI。
相對于類型轉換安全的上溯造型,下溯造型就未必是安全的了。我們經常會做些強制類型轉換的事情,有時我們也會無意間遇到 ClassCastException的轉換異常(從這一點來說,我們應該多用範型來避免不安全的類型轉換)。例如:
public static void downcasting(Animal animal){
//判斷類是不是實作自哪個接口
if(animal instanceof DoorGod){
DoorGod doorGod = (DoorGod)animal;
doorGod.guard();
}
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.speak();
}
}
如果沒有采取措施(上面使用的措施是instanceof)判斷對象的類型,那麼向下的強制轉換就是不安全的。這種轉換錯誤在編譯時是不能檢測出來的,隻有在運作時才會抛出 ClassCastException異常,對于測試來說,這樣的錯誤也是很難檢測的。
6、抽象類的由來----限制基礎類隻為自己的衍生類提供一個接口,不允許建立一個自己的執行個體
7、對象的建立和存在時間----對象需要的資料位于哪兒,如何控制對象的“存在時間”呢?
一種是編寫程式确定存儲及存在時間。如:C++認為程式的執行效率是最重要的一個問題。為獲得最快的運作速度,存儲以及存在時間可在編寫程式時決定。這樣便為存儲空間的配置設定和釋放提供了一個優先級。這種優先級的控制是非常有價值的。然而,我們同時也犧牲了靈活性,因為在編寫程式時,必須知道對象的準确的數量、存在時間、以及類型。
另一種記憶體池中動态建立對象。若需一個新對象,隻需在需要它的時候在記憶體堆裡簡單地建立它即可。由于存儲空間的管理是運作期間動态進行的,是以在記憶體堆裡配置設定存儲空間的時間比在堆棧裡建立的時間長得多(在堆棧裡建立存儲空間一般隻需要一個簡單的指令,将堆棧指針向下或向下移動即可)。除此以外,更大的靈活性對于正常程式設計問題的解決是至關重要的。
程式員可用兩種方法來破壞一個對象:用程式化的方式決定何時破壞對象,或者利用由運作環境提供的一種“垃圾收集器”特性,自動尋找那些不再使用的對象,并将其清除。當然,垃圾收集器顯得友善得多,但要求所有應用程式都必須容忍垃圾收集器的存在,并能默許随垃圾收集帶來的額外開銷。
7.1集合與繼承器----不知道需要對象個數、存活時間,如何儲存呢?
會自動擴充的集合對象,容納指向其他對象的位址。
最後
持續更新中…