【java基礎(二十五)】類、超類、子類(三)

阻止繼承:final類和方法

有時候,可能希望阻止人們利用某個類定義子類。不允許擴展的類稱爲final類。如果在定義類的時候使用了final修飾符就表明這個類是final類。例如,假設希望阻止定義Executive類的子類,就可以在定義這個類的時候,使用final修飾符聲明:

public final class Executive extends Manager {
	...
}

類中的特定方法也可以被聲明爲final。如果這樣做,子類就不能覆蓋這個方法(final類的所有方法自動地成爲final放方法),如:

public class Employee {
	public final String getName() {
		return name;
	}
}

將方法或類聲明爲final主要目的是:確保它們不會在子類中改變語義。例如,Calendar類中的getTime和setTime方法都聲明爲final。這表明Calendar類的設計者負責實現Date類與日誌狀態之間的轉換,而不允許子類處理這些問題。同樣的,String類也是final類,這意味着不允許任何人定義String的子類。換言之,如果有一個String的引用,它引用的一定是一個String對象,而不可能是其他類的對象。
有些程序猿認爲:除非有足夠的理由使用多態性,應該將所有的方法都聲明爲final。事實上,在C++和C#中,如果沒有特別的聲明,所有的方法都不具有多態性。這兩種做法可能都有些偏激。我們提倡在設計類層次時,仔細地思考應該將哪些方法和類聲明爲final。
在早起的Java中,有些程序員爲了避免動態綁定帶來的系統開銷而使用final關鍵字。如果一個方法沒有被覆蓋並且很短,編譯器就能夠對它進行優化處理,這個過程稱爲內聯(inlining)。例如,內聯調用e.getName()將替換爲防衛e.name域。這是一項很有意義的改變,這是由於CPU在處理調用方法的指令時,使用的分支轉移會擾亂預期指令的策略,所以,這被視爲不受歡迎的。然而,如果getName在另外一個類中被覆蓋,那麼編譯器就無法知道覆蓋的代碼將會做什麼操作,因此也就不能對它進行內聯處理了。
幸運的是,虛擬機中的即使編譯器比傳統編譯器的處理能力強的多。這種編譯器可以準確的知道類之間的繼承關係,並能夠檢測出類中是否真正地存在覆蓋給定的方法。如果方法很簡單、被頻繁調用且沒有真正的被覆蓋,那麼即時編譯器就會對這個方法進行內聯處理。如果虛擬機加載了另一個子類,而在這個子類中包含了對內聯方法的覆蓋,那麼將會發生什麼情況呢?優化器將取消對覆蓋方法的內聯。這個過程很慢,但卻很少發生。

強制類型轉換

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

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

將表達式x的值轉換成整數類型,捨棄了小數部分。
正像有時候需要將浮點型數值轉換成整數數值一樣,有時候也可能需要將某個類的對象引用轉換成另外一個對象的引用。對象引用的轉換語法與數值表達式的類型轉換類似,近需要用一對圓括號將目標類包括起來,並放置在需要轉換的對象引用之前就可以了,如:

Manager boss = (Manager)staff[0];

進行類型轉換的唯一原因是:在暫時忽視對象的實際類型之後,使用對象的全部功能。如,在ManagerTest類中,由於某些項是普通員工,所以staff數組必須是Employee對象的數組。我們需要將數組中引用經理的元素復原成Manager類,以便能夠訪問新增加的所有變量。
大家知道,在Java中,每個對象變量都屬於一個類型。類型描述了這個變量所引用的以及能夠引用的對象類型。
將一個值存入變量時,編譯器將檢查是否允許該操作。將一個子類的引用賦給一個超類變量,編譯器是允許的。但講一個超類的引用賦給一個子類變量,必須進行類型轉換,這樣才能夠夠通過運行時的檢查。
如果試圖在繼承連上進行向下的類型轉換,並且“謊報”有關對象包含的內容,會發生什麼呢?

Manager boss = (Manager)staff[1];

運行這個程序時,Java運行時系統將報告一個錯誤,併產生一個ClassCastException異常。如果沒有捕獲這個異常,那麼程序就會終止。因此,應該養成這樣一個良好的程序設計習慣:在進行類型轉換之前,先查看一下是否能夠成功地轉換,這個過程簡單的使用instanceof操作符就可以實現。例如:

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

最後,如果這個類型轉換不可能成功,編譯器就不會進行這個轉換。例如:

String c = (String)staff[1];

將會產生編譯錯誤,這是因爲String不是Employee的子類。
綜上所述:

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

實際上,通過類型轉換調整對象的類型並不是一種好的做法。在我們列舉的示例中,大多數情況並不需要將Employee對象轉換成Manager對象,兩個類的對象能夠正確地調用getSalary方法,這是因爲實現多態性的動態綁定機制能夠自動地找到相應的方法。
只有在使用Manager中特有的方法時才需要進行類型轉換,例如,setBonus方法。如果鑑於某種原因,發現需要通過Employee對象調用setBonus方法,那麼就應該檢查一下超類的設計是否合理。重新設計一下超類,並添加setBonus方法纔是正確的選擇。請記住,只要沒有捕獲ClassCastException異常,程序就會終止執行。在一般情況下,應該儘量少用類型轉換和instanceof運算符。

捐贈

若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章