java核心技術卷I-繼承

繼承

人們可以基於已存在的類構造一個新類。繼承已存在的類就是複用(繼承)這些類的方法和域。在此基礎上,還可以添加一些新的方法和域, 以滿足新的需求。

術語

關鍵字 extends 表明正在構造的新類派生於一個已存在的類。 已存在的類稱爲超類
( superclass)、 基類( base class) 或父類(parent class); 新類稱爲子類(subclass、) 派生類( derived class) 或孩子類(child class)。 超類和子類是 Java 程序員最常用的兩個術語,而瞭解其他語言的程序員可能更加偏愛使用父類和孩子類。
子類比超類擁有的功能更加豐富。
在通過擴展超類定義子類的時候,僅需要指出子類與超類的不同之處。因此在設計類的時候,應該將通用的方法放在超類中, 而將具有特殊用途的方法放在子類中,這種將通用的功能放到超類的做法,在面向對象程序設計中十分普遍。

覆蓋方法(override)

調用超類中的方法, 而不是當前類的這個方法。爲此, 可以使用特定的關鍵字 super 解決這個問題

super.getSalary()

有些人認爲 super 與 this 引用是類似的概念, 實際上,這樣比較並不太恰當。這是因爲 super 不是一個對象的引用, 不能將 super 賦給另一個對象變量, 它只是一個指示編譯器調用超類方法的特殊關鍵字。
在覆蓋一個方法的時候,子類方法不能低於超類方法的可見性。

子類構造器

由於子類的構造器不能訪問超類的私有域, 所以必須利用超類的構造器對這部分私有域進行初始化,我們可以通過 super 實現對超類構造器的調用。使用super 調用構造器的語句必須是子類構造器的第一條語句。
如果子類的構造器沒有顯式地調用超類的構造器, 則將自動地調用超類默認(沒有參數 )的構造器。 如果超類沒有不帶參數的構造器, 並且在子類的構造器中又沒有顯式地調用超類的其他構造器,則 Java 編譯器將報告錯誤。

public Manager(String name, double salary, int year, int month, int day) {
	super(name, salary, year, month, day);
	bonus = 0; 
}

多態

一個對象變量(例如, 變量 e ) 可以指示多種實際類型的現象被稱爲多態( polymorphism)。

Employee e; 
e = new Employee(. . .); // Employee object expected
e = new Manager(. . .); // OK, Manager can be used as well

在 Java 程序設計語言中,對象變量是多態的。 一個 Employee 變量既可以引用一個Employee 類對象, 也可以引用一個 Employee 類的任何一個子類的對象(例如, Manager、Executive、Secretary 等)
然而,不能將一個超類的引用賦給子類變量。

動態綁定

在運行時能夠自動地選擇調用哪個方法的現象稱爲動態綁定(dynamic binding)。動態綁定有一個非常重要的特性: 無需對現存的代碼進行修改,就可以對程序進行擴展。假設增加一個新類 Executive, 並且變量 e 有可能引用這個類的對象, 我們不需要對包含調用e.getSalary() 的代碼進行重新編譯。如果 e 恰好引用一個 Executive 類的對象,就會自動地調用 Executive.getSalaryO 方法。

理解方法調用

1 ) 編譯器査看對象的聲明類型和方法名。假設調用 x.f(param) 且隱式參數 x 聲明爲 C類的對象。需要注意的是:有可能存在多個名字爲 f, 但參數類型不一樣的方法。例如,可能存在方法 f(int) 和方法 f(String) 編譯器將會一一列舉所有 C 類中名爲 f 的方法和其超類中訪問屬性爲 public 且名爲 f 的方法(超類的私有方法不可訪問)。
至此, 編譯器已獲得所有可能被調用的候選方法。
2 ) 接下來,編譯器將査看調用方法時提供的參數類型。如果在所有名爲 f 的方法中存在一個與提供的參數類型完全匹配,就選擇這個方法。這個過程被稱爲重栽解析( overloading resolution)。例如,對於調用 x.f(“ Hello” )來說, 編譯器將會挑選 f(String) 而不是 f(int)。由於允許類型轉換( int 可以轉換成 double, Manager 可以轉換成 Employee, 等等,) 所以這個過程可能很複雜。如果編譯器沒有找到與參數類型匹配的方法, 或者發現經過類型轉換後有多個方法與之匹配, 就會報告一個錯誤。
至此, 編譯器已獲得需要調用的方法名字和參數類型。
3 ) 如果是 private 方法、 static 方法、 final 方法或者構造器, 那麼編譯器將可以準確地知道應該調用哪個方法, 我們將這種調用方式稱爲靜態綁定( static binding )。 與此對應的是,調用的方法依賴於隱式參數的實際類型, 並且在運行時實現動態綁定。在我們列舉的示例中, 編譯器採用動態綁定的方式生成一條調用 f (String) 的指令。
4 ) 當程序運行,並且採用動態綁定調用方法時, 虛擬機一定調用與 x 所引用對象的實際類型最合適的那個類的方法。假設 x 的實際類型是 D,它是 C 類的子類。如果 D 類定義了方法 f(String) 就直接調用它;否則, 將在 D 類的超類中尋找 f(String) 以此類推。
每次調用方法都要進行搜索,時間開銷相當大。因此,虛擬機預先爲每個類創建了一個方法表( method table), 其中列出了所有方法的簽名和實際調用的方法。這樣一來,在真正調用方法的時候, 虛擬機僅查找這個表就行了。在前面的例子中, 虛擬機搜索 D 類的方法表, 以便尋找與調用 f(Sting) 相K配的方法。這個方法既有可能是 D.f(String), 也有可能是X.f(String), 這裏的 X 是 D 的超類。這裏需要提醒一點,如果調用super.f(param), 編譯器將對隱式參數超類的方法表進行搜索。方法表如下:

Employee:
getName()> Employee.getName()
getSalary() -> Employee.getSalary()
getHireDay()-> Employee.getHireDay()
raiseSalary(double) -> Employee.raiseSalary(doubl e)

在運行時, 調用 e.getSalaryO 的解析過程爲:
1 ) 首先,虛擬機提取 e 的實際類型的方法表。既可能是 Employee、 Manager 的方法表,也可能是 Employee 類的其他子類的方法表。
2 ) 接下來, 虛擬機搜索定義 getSalary 簽名的類。此時,虛擬機已經知道應該調用哪個方法。
3 ) 最後,虛擬機調用方法

阻止繼承:final 類和方法

有時候,可能希望阻止人們利用某個類定義子類。不允許擴展的類被稱爲 final 類。如果在定義類的時候使用了 final 修飾符就表明這個類是 final 類。例如, 假設希望阻止人們定義Executive類的子類,就可以在定義這個類的時候使用 final 修飾符聲明。
類中的特定方法也可以被聲明爲 final。如果這樣做,子類就不能覆蓋這個方法( final 類)中的所有方法自動地成爲 final 方法 。

強制類型轉換

將一個類型強制轉換成另外一個類型的過程被稱爲類型轉換。Java 程序設計語言提供了一種專門用於進行類型轉換的表示法。

double x = 3.405;
int nx = (int) x ;

將一個值存人變量時, 編譯器將檢查是否允許該操作。將一個了-類的引用賦給一個超類變量, 編譯器是允許的。但將一個超類的引用賦給一個子類變量, 必須進行類型轉換, 這樣才能夠通過運行時的檢査。
應該養成這樣一個良好的程序設計習慣:在進行類型轉換之前,先查看一下是否能夠成功地轉換。這個過程簡單地使用instanceof 操作符就可以實現。

if (staff[1] instanceof Manager) {
boss = (Manager) staff[1]:
...
}

綜上所述:
只能在繼承層次內進行類型轉換。
在將超類轉換成子類之前,應該使用 instanceof進行檢查。

抽象類

如果自下而上在類的繼承層次結構中上移,位於上層的類更具有通用性,甚至可能更加抽象。從某種角度看, 祖先類更加通用, 人們只將它作爲派生其他類的基類,而不作爲想使用的特定的實例類。
使用 abstract 關鍵字,這樣就完全不需要實現這個方法

public abstract String getDescription();

包含一個或多個抽象方法的類本身必須被聲明爲抽象的。除了抽象方法之外,抽象類還可以包含具體數據和具體方法。
抽象方法充當着佔位的角色, 它們的具體實現在子類中。擴展抽象類可以有兩種選擇。
一種是在抽象類中定義部分抽象類方法或不定義抽象類方法,這樣就必須將子類也標記爲抽象類;另一種是定義全部的抽象方法,這樣一來,子類就不是抽象的了。
類即使不含抽象方法,也可以將類聲明爲抽象類。
抽象類不能被實例化。

可見性

1 ) 僅對本類可見 private。
2 ) 對所有類可見 public。
3 ) 對本包和所有子類可見 protected。
4 ) 對本包可見—默認(很遺憾) 不需要修飾符。

繼承的設計技巧

  1. 將公共操作和域放在超類
  2. 不要使用受保護的域
  3. 使用繼承實現“ is-a” 關係
  4. 除非所有繼承的方法都有意義,否則不要使用繼承
  5. 在覆蓋方法時,不要改變預期的行爲
  6. 使用多態, 而非類型信息
  7. 不要過多地使用反射
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章