Java零基礎學習之路(十三)Java super
ava super的定義
本章節目標:
掌握super都可以用在哪裡?理解super在記憶體方面的儲存位置。掌握怎麼通過子類的構造方法呼叫父類的構造方法。super什麼時候可以省略,什麼時候不能省略
知識框架:
super和this可以對比著學習:
this
● this是一個引用,儲存記憶體地址指向自己。
● this出現在例項方法中,誰呼叫這個例項方法,this就代表誰,this代表當前正在執行這個動作的物件。
● this不能出現在靜態方法中。
● this大部分情況下可以省略,在方法中區分例項變數和區域性變數的時候不能省略。
●“this(實際引數列表)”出現在構造方法第一行,通過當前的構造方法去呼叫本類當中其它的構造方法。
super
嚴格來說,super其實並不是一個引用,它只是一個關鍵字,super代表了當前物件中從父類繼承過來的那部分特徵。this指向一個獨立的物件,super並不是指向某個“獨立”的物件,假設張大明是父親,張小明是兒子,有這樣一句話:大家都說張小明的眼睛、鼻子和父親的很像。那麼也就是說兒子繼承了父親的眼睛和鼻子特徵,那麼眼睛和鼻子肯定最終還是長在兒子的身上。假設this指向張小明,那麼super就代表張小明身上的眼睛和鼻子。換句話說super其實是this的一部分。如下圖所示:張大明和張小明其實是兩個獨立的物件,兩個物件記憶體方面沒有聯絡,super只是代表張小明物件身上的眼睛和鼻子,因為這個是從父類中繼承過來的,在記憶體方面使用了super關鍵字進行了標記,對於下圖來說“this.眼睛”和“super.眼睛”都是訪問的同一塊記憶體空間。
圖14-1:super記憶體圖
● super和this都可以使用在例項方法當中。
● super不能使用在靜態方法當中,因為super代表了當前物件上的父型別特徵,靜態方法中沒有this,肯定也是不能使用super的。
● super也有這種用法:“super(實際引數列表);”,這種用法是通過當前的構造方法呼叫父類的構造方法。
接下來,我們來測試一下:
-
public class SuperTest01 extends Object{ -
//例項方法 -
public void doSome(){ -
System.out.println(this); -
System.out.println(super); -
} -
}
編譯報錯了:
圖14-2:this可以單獨輸出,super不能
通過以上的測試,可以看出this是可以單獨使用的引用,但super無法輸出,編譯器提示super要使用必須是“super.xxx”,顯然super並不指向獨立的物件,並不是儲存某個物件的記憶體地址。
再來看另外的一個測試:
-
public class SuperTest02 extends Object{ -
//靜態方法 -
public static void doSome(){ -
System.out.println(this); -
System.out.println(super.toString()); -
} -
}
編譯報錯了:
圖14-3:this和super都不能使用在靜態方法中
通過以上的測試,可以看出this和super都是無法使用在靜態方法當中的。
Java中super關鍵字的使用(在構造方法中)
super使用在構造方法中,語法格式為:super(實際引數列表),這行程式碼和“this(實際引數列表)”都是隻允許出現在構造方法第一行(這一點記住就行了),所以這兩行程式碼是無法共存的。“super(實際引數列表)”這種語法表示子類構造方法執行過程中呼叫父類的構造方法。我們來看一段程式碼:
-
public class People { -
String idCard; -
String name; -
boolean sex; -
public People(){ -
} -
public People(String idCard,String name,boolean sex){ -
this.idCard = idCard; -
this.name = name; -
this.sex = sex; -
} -
}
-
public class Student extends People{ -
//學號是子類特有的 -
int sno; -
public Student(){ -
} -
public Student(String idCard,String name,boolean sex,int sno){ -
this.idCard = idCard; -
this.name = name; -
this.sex = sex; -
this.sno = sno; -
} -
}
-
public class StudentTest { -
public static void main(String[] args) { -
Student s = new Student("12345x","jack",true,100); -
System.out.println("身份證號" + s.idCard); -
System.out.println("姓名" + s.name); -
System.out.println("性別" + s.sex); -
System.out.println("學號" + s.sno); -
} -
}
執行結果如下圖所示:
圖14-4:執行結果
我們把上面的程式碼片段拿過來放在一起看看:
父類的構造方法:
-
public People(String idCard,String name,boolean sex){ -
this.idCard = idCard; -
this.name = name; -
this.sex = sex; -
}
子類的構造方法:
-
public Student(String idCard,String name,boolean sex,int sno){ -
this.idCard = idCard; -
this.name = name; -
this.sex = sex; -
this.sno = sno;
你有沒有察覺到子類的構造方法前三行程式碼和父類構造方法中的程式碼是一樣的?接下來把子類的構造方法修改一下,然後再執行測試程式:
-
public Student(String idCard,String name,boolean sex,int sno){ -
super(idCard,name,sex); -
this.sno = sno; -
}
執行結果如下圖所示:
圖14-5:執行結果
通過以上程式碼的學習,“super(實際引數列表);”語法表示呼叫父類的構造方法,程式碼複用性增強了,另外一方面也是模擬現實世界當中的“要想有兒子,必須先有父親”的道理。不過這裡的“super(實際引數列表)”在呼叫父類構造方法的時候,從本質上來說並不是建立一個“獨立的父類物件”,而是為了完成當前物件的父型別特徵的初始化操作。(或者說通過子類的構造方法呼叫父類的構造方法,是為了讓張小明身上長出具有他父親特點的鼻子和眼睛,鼻子和眼睛初始化完畢之後,具有父親的特點,但最終還是長在張小明的身上)。
接下來,再來看一段程式碼:
-
public class A { -
public A(){ -
System.out.println("A類的無引數構造方法執行"); -
} -
}
-
public class B extends A { -
public B(){ -
System.out.println("B類的無引數構造方法執行"); -
} -
}
-
public class C extends B { -
public C(){ -
System.out.println("C類的無引數構造方法執行"); -
} -
}
-
public class Test { -
public static void main(String[] args) { -
new C(); -
} -
}
執行結果如下圖所示:
圖14-6:super()的測試
通過以上執行結果可以得出以下的等效程式碼:
-
public class A { -
public A(){ -
//這裡呼叫的是Object類中的無引數構造方法 -
//因為A類的父類是Object -
super(); -
System.out.println("A類的無引數構造方法執行"); -
} -
}
-
public class B extends A { -
public B(){ -
super(); -
System.out.println("B類的無引數構造方法執行"); -
} -
}
-
public class C extends B { -
public C(){ -
super(); -
System.out.println("C類的無引數構造方法執行"); -
} -
}
執行結果如下圖所示:
圖14-7:super()的測試
通過以上程式碼的測試我們得出,當一個構造方法第一行沒有顯示的呼叫“super(實際引數列表)”的話,系統預設呼叫父類的無引數構造方法“super()”。當然前提是“this(實際引數列表)”也沒有顯示的去呼叫(因為super()和this()都只能出現在構造方法第一行,所以不能並存)。我們可以通過以下程式再次測試一下:
-
public class A { -
//有引數構造方法定義之後 -
//系統則不再提供無引數構造方法 -
public A(String s){ -
} -
}
-
public class B extends A { -
public B(){ -
} -
}
編譯報錯了:
圖14-8:編譯報錯資訊
以上程式為什麼會編譯報錯呢?原因是B類的構造方法第一行預設會呼叫“super()”,而super()會呼叫父類A的無引數構造方法,但由於父類A中提供了有引數構造方法,導致無引數構造方法不存在,從而編譯報錯了。所以在實際開發中還是建議程式設計師將無引數構造方法顯示的定義出來,這樣就可以避免物件的建立失敗了。
另外,通過以上內容的學習,還可以得出這樣的結論:在java語言當中無論是建立哪個java物件,老祖宗Object類中的無引數構造方法是必然執行的。
接下來我們再來看一下:一個java物件在建立過程中比較完整的記憶體圖是如何變化的,請先看以下程式碼:
-
public class People { -
String name; -
boolean sex; -
public People(String name,boolean sex){ -
this.name = name; -
this.sex = sex; -
} -
}
-
public class Worker extends People { -
//子類特有的工資屬性 -
double salary; -
public Worker(String name,boolean sex,double salary){ -
super(name,sex); -
this.salary = salary; -
} -
}
-
public class WorkerTest { -
public static void main(String[] args) { -
Worker w = new Worker("jack",true,10000.0); -
System.out.println("姓名:" + w.name); -
System.out.println("性別:" + w.sex); -
System.out.println("工資:" + w.salary); -
} -
}
執行結果如下圖所示:
圖14-9:執行結果
以上程式建立Worker物件時構造方法的執行順序是:
● 先執行Object類的無引數構造方法;
● 再執行People類的構造方法;
● 最後執行Worker類的構造方法;
注意:雖然執行了三個構造方法,但是物件實際上只建立了一個Worker。
以上程式的記憶體結構圖是這樣變化的:
圖14-10:程式執行記憶體圖
通過以上內容的學習,super()的作用主要是:第一,呼叫父類的構造方法,使用這個構造方法來給當前子類物件初始化父型別特徵;第二,程式碼複用。
Java中super關鍵字的使用(在例項方法中)
super和this都可以使用在例項方法中,並且都不能使用在靜態方法當中,“this”大部分情況下都是可以省略的,只有在方法中區分區域性變數和例項變數的時候不能省略。那“super”什麼時候可以省略,什麼時候不能省略呢?
-
//書 -
public class Book { -
//書名 -
String name; -
//構造方法 -
public Book(){ -
super(); -
} -
public Book(String name){ -
super(); -
this.name = name; -
} -
}
-
//紙質書 -
public class PaperBook extends Book { -
//構造方法 -
public PaperBook(){ -
super(); -
} -
public PaperBook(String name){ -
super(); -
this.name = name; -
} -
//列印書名 -
public void printName(){ -
System.out.println("this.name->書名 : " + this.name); -
System.out.println("super.name->書名 : " + super.name); -
} -
}
-
public class BookTest { -
public static void main(String[] args) { -
PaperBook book1 = new PaperBook("零基礎學Java卷I"); -
book1.printName(); -
} -
}
執行結果如下圖所示:
圖14-11:super和this
我們發現printName()方法中的super.name和this.name最終輸出結果是一樣的,這是為什麼呢?請看以上程式執行的記憶體圖:
圖14-12:父類的構造方法執行結束之後的記憶體圖
圖14-13:子類的構造方法執行結束之後的記憶體圖
通過以上記憶體結構圖發現this.name和super.name實際上是同一塊記憶體空間,所以它們的輸出結果是完全一樣的。接下來,我們再把以上的PaperBook類修改一下:
-
//紙質書 -
public class PaperBook extends Book { -
String name; //在子類中也定義了一個name屬性 -
//構造方法 -
public PaperBook(){ -
super(); -
} -
public PaperBook(String name){ -
super(); -
this.name = name;//這裡的this.name代表子類的name -
} -
//列印書名 -
public void printName(){ -
System.out.println("this.name->書名 : " + this.name); -
System.out.println("super.name->書名 : " + super.name); -
} -
}
執行結果如下圖所示:
圖14-14:super和this的區別
為什麼super.name是null呢,我們一起來看看以上程式的記憶體圖:
圖14-15:父類Book的構造方法執行之後的記憶體圖
圖14-16:子類PaperBook的構造方法執行結束之後的記憶體圖
通過以上記憶體圖可以清楚的看到,父類Book的構造方法在執行的時候給super.name賦值null,子類PaperBook的構造方法在執行的時候給this.name賦值“零基礎學Java卷I”,由於在子類PaperBook中定義了重名的變數name導致在當前物件中有兩個name,一個是從父類中繼承過來的,一個是自己的,如果此時想訪問父類中繼承過來的name則必須使用super.name,當直接訪問name或者this.name都表示訪問當前物件自己的name。
通過以上的學習,大家知道super在什麼情況下不能省略了嗎?當父類中有該例項變數,子類中又重新定義了同名的例項變數,如果想在子類中訪問父類的例項變數,super不能省略。例項方法是這樣嗎?我們可以來測試一下,請看程式碼:
-
public class Vip { -
//Vip預設繼承Object -
//重寫從Object類中繼承過來的toString()方法 -
public String toString(){ -
return "我是金牌會員"; -
} -
public void test(){ -
System.out.println(super.toString()); -
System.out.println(this.toString()); -
System.out.println(toString()); -
} -
}
-
public class VipTest { -
public static void main(String[] args) { -
Vip vip = new Vip(); -
vip.test(); -
} -
}
