【1】構造函數
爲了在遵守某些約定的情況下對已有的程序進行擴充,java語言和一般的op語言一樣擁有繼承。繼承是爲了擴展,繼承不是爲了修改。
這裏我們談幾點java繼承機制中容易忽略但是很重要的幾點。
1.子類中的構造函數
假如我們的超類中顯示聲明瞭一個構造函數,子類的實例化能用默認的構造函數麼?答案是不能! 比如下面這個例子,子類中必須顯示聲明。
public class third {
public static void main(String atgs[])
{
a a1=new a(1, "i am class a");
System.out.print(a1.id+" "+a1.name);
}
}
class a extends b{
a(int i,String n){//超類沒有不帶參數的構造函數 所以必須顯示聲明
super(i, n);
}
}
class b{
public int id;
public String name;
b(int i,String n){
id=i;
name=n;
}
}
我們得弄清楚構造函數不是方法也不可以被繼承,所以依賴與超類的子類必須傳遞參數給超類。
2.構造函數的形式
上面我們講了構造函數不是方法,那麼他是什麼?其實沒有確切的準確的形容詞來概括他,實際上類的初始化工作可以有不同的形式。
形式一 構造函數
形式二 定義的時候初始化(這在c++裏面是不允許的,但是java可以)
形式三 構造塊 這個比較特殊,用{}在類中來表示這是構造塊,它可以執行普通語句 就和在函數裏寫一樣 ,但是不需要定義方法名。
class b{
public int id=0;//定義初始化
public String name;
{//初始化塊
System.out.println(id);
System.out.println(name);
}
b(){//無參數構造函數
}
b(int i,String n){//帶參數構造函數
id=i;
name=n;
}
}
3.構造函數的順序依賴
這個是老話題了,遞歸調用,從object類開始。
【2】繼承和再定義成員
1.重載和覆蓋方法
有的時候我們需要保留超類的某個函數並且對它的適用範圍進行擴展,這時候我們就用到重載,重載的方法很簡單:聲明和超類同名但是參數不同的方法即可。
覆蓋適用於不想保留超類方法的時候,我們可以在子類中用一個同名同參數的方法去覆蓋超類中的方法。
覆蓋的時候我們遵循子類不可以修改超類的約定,對於方法的訪問權限只能是越來越寬鬆,比如可以從private修改到public,因爲這被視作是子類對超類的擴展。但是反過來就不行,因爲這樣修改了超類的協議,超類中原本可以訪問的方法子類卻把它屏蔽了。因爲如果我們使用超類構造一個對象但是我們實際引用了子類的實例,那麼原來父類中private的方法會暴露爲public,但是這不影響正確性,反過來就不行。
例如下面的代碼是不會編譯通過的
class a extends b{
a(int i,String n){//超類沒有不帶參數的構造函數 所以必須顯示聲明
super(i, n);
}
private void b_method(){
System.out.print("a overload b_method!");
}
}
class b{
public void b_method() {
}
b(int i,String n){//帶參數構造函數
id=i;
name=n;
}
}
2.隱藏域
域不會被覆蓋,但是它可以被隱藏。如果在子類中聲明和超類一樣的域超類中的域依然會存在,但是不能直接通過域名去訪問他。必須通過super或者是超類的引用去訪問他。
如下:
public class third {
public static void main(String atgs[])
{
b a1=new a(1, "a");//超類引用子類
a a2=new a(2, "a");//子類
System.out.println(a1.classname);
System.out.println(a2.classname);
}
}
class a extends b{
public String classname="class a";
a(int i,String n){//超類沒有不帶參數的構造函數 所以必須顯示聲明
super(i, n);
}
public void b_method(){
System.out.print("a overload b_method!");
}
}
class b{
public String classname="class b";
public int id=0;//定義初始化
public String name;
public void b_method() {
}
{//初始化塊
System.out.println(id);
System.out.println(name);
}
b(){//無參數構造函數
}
b(int i,String n){//帶參數構造函數
id=i;
name=n;
}
}
但是方法的訪問是不一樣的,總的來說引用類型決定域,真實類型決定方法。這樣就造成了不統一的問題,假如說我用一個超類類型引用子類對象,那麼這個引用的域是超類的域,方法是子類的方法!!如下所示:
public static void main(String atgs[])
{
b a1=new a(1, "a");//超類引用子類
System.out.println(a1.classname);
a1.b_method();
}
}
//輸出結果:
class b
a overload b_method!
正因爲這個原因,我們鼓勵通過存取器(get/set函數)來操縱超類的域,因爲一個子類當中的方法訪問和超類中相同名字的域的時候他會選擇本類中的域而不會去訪問超類中的同名域,也就是說如果我們通過 a1.get_classnam()來獲取域的話得到的就是class a。這樣方法和域都是具體類的方法和域!!
【3】類型兼容和轉換
1.兼容性
在類型層次中,層次越高兼容性越廣。因爲類型層次越高越不具體,覆蓋範圍越廣。類型層次越低越具體,細節越豐富,覆蓋範圍越窄。
比如說:動物是超類 貓,狗是他的子類
動物兼容貓和狗,所以我們可以 貓 貓1=new 動物(),狗 狗2=new 動物()·····
反過來就不行,因爲你不能說動物是狗。當一個高次的類型引用低層次的對象的時候 不會發生問題,這種轉換我們稱做寬轉換。相反就會發生問題,稱作窄轉換。
2.類型測試
通過使用 instanceof 運算符就可以檢查對象的類,如果其左邊的表達式與右邊的類型名是賦值兼容的話那麼就會返回 true 否則返回 false 。
instanceof用來做一具體的類型判斷,在網上看到一個例子還是挺好的。
instanceof有一些用處。比如我們寫了一個處理賬單的系統,其中有這樣三個類:
public class Bill {//省略細節}
public class PhoneBill extends Bill {//省略細節}
public class GasBill extends Bill {//省略細節}
在處理程序裏有一個方法,接受一個Bill類型的對象,計算金額。假設兩種賬單計算方法不同,而傳入的Bill對象可能是兩種中的任何一種,所以要用instanceof來判斷:
public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//計算電話賬單
}
if (bill instanceof GasBill) {
//計算燃氣賬單
}
...
}
這樣就可以用一個方法處理兩種子類。
然而,這種做法通常被認爲是沒有好好利用面向對象中的多態性。其實上面的功能要求用方法重載完全可以實現,這是面向對象變成應有的做法,避免回到結構化編程模式。只要提供兩個名字和返回值都相同,接受參數類型不同的方法就可以了:
public double calculate(PhoneBill bill) {
//計算電話賬單
}
public double calculate(GasBill bill) {
//計算燃氣賬單
}
所以,使用instanceof在絕大多數情況下並不是推薦的做法,應當好好利用多態。
3.protected的確切含義
protected設計被用於一些“超類只對子類和同一個包的代碼開放訪問”的場合。
這裏有幾點要注意的
1.不同包,類自身的實例能訪問protected成員麼?不能!!假如我在package a裏面有一個類class_a 我在另一個package裏面導入package a並且實例化了一個class_a對象,這個對象是無法訪問protected成員的。聽起來有些匪夷所思,但是的確是這樣的。
2.不同包,子類能訪問超類的protected成員麼?可以!!!
3.不同子類能通過對方訪問相同父類的protected成員麼? 不可以!!!
總的來說,只有子類內部或者是同一個包可以訪問超類的protected成員,這與設計protected這個類型的初衷是一致的!!!
【4】Object類
object類是所有類的基類,位於最頂層。object類裏面有所有類都具有的方法,他們分別是equals,hashcode,clone,getclass,finalize,tostring。用法不一一詳細講,這裏面比較特別的是equals,clone,hashcode。
1.equals方法區別兩個對象是否在內容上相同,注意是內容!!!‘==’用於判斷兩個對象是否指向同一個引用。
2.clone 方法返回一個與原對象指向同一類型的對象,但是需要注意的是這個對象只是原對象的淺拷貝,要想深拷貝原對象需要重載clone方法。(注意clone方法本身是protected的!!)
3.hashcode需要注意是因爲他和equals的關係,這個需要大家回想一下hash存儲算法。hash存儲算法中,我們每次存儲數據都會根據數據生成一個hashcode,這個code下面可能已經存有數據了,如果現有的數據和要存儲的數據相同(注意:這裏的相同判斷就是用 equals !!!)那麼我們就放棄存儲。
【5】單繼承和多繼承
關於單繼承和多繼承 java的答案是隻有單繼承。爲什麼不支持多繼承,主要原因是爲了規避一些多繼承帶來的風險。這裏有個很好的例子,假如超類有方法fun,超類有兩個子類a,b他們重載了方法fun。這時候有個類c繼承了a和b,那麼c裏面的fun方法該來自誰呢?就好像避免精神分裂帶來的危險一樣,子類的特點必須和唯一的超類一致。
爲了解決擯棄多繼承的缺點,所以java裏面引進了接口。