java過載方法和類初始化詳解
1 過載問題
我們先看兩段程式碼:
public classTest2 { public static void main(String[] args) { f1(null); f2(); } public static void f1(String s) { System.out.println("執行哪個方法?我是String"); } public static void f1(Object o) { System.out.println("執行哪個方法?我是Object"); } public static void f2(){ System.out.println("執行哪個方法?我是無引數"); } public static void f2(String...strings){ System.out.println("執行哪個方法?我是不定長引數"); } }
1.1 過載中null和有形參
我們在呼叫f1(null)
,理論上呼叫兩個方法都是可以執行的,但是jvm
肯定不能兩個方法都執行,只能選擇其中的一個,它會選擇哪個?jvm
會選擇 String
引數的方法,因為根據方法過載中準確性的原則,從層次上看Object
處在更上層,String
是從Object
繼承過來的,呼叫String
將更準確。
1.2 過載中string和stringbuffer
如果我再加一個方法
public static void f1(StringBuffer s) { System.out.println("執行哪個方法?我是String"); }
這時再呼叫f1(null);
就不能通過編譯,為什麼呢?由於StringBuffer
和String
並沒有繼承上的關係,因此編譯器感覺StringBuffer
和String
作為引數的方法都很準確,它就不知道該執行哪個方法了,會出現編譯錯誤,違反了過載中唯一性的原則
1.3 過載中無參和不定長引數
而我們在呼叫f2();
方法時,jvm
又會執行哪個?答案是無引數的。其實不定長引數在編譯器編譯之後它會將這個f2(String...strings)
編譯成引數為陣列的方法,我們可以通過程式碼證明:
Class clazz=Test2.class; Method[]methods=clazz.getDeclaredMethods(); for (Method method :methods) { System.out.println(method); }
結果為:public static void Test2.f2(java.lang.String[])
所以直接呼叫不傳引數呼叫f2()
自然就是呼叫無引數方法最正確。而如果沒有f2(){......};
這個方法我們呼叫f2()
依然可以執行,這是因為不定長引數支援無引數
,但是這裡的支援無引數其實是編譯器自動幫我們填充一個new String[]{""}
的形式呼叫,所以,本質上來講,就是一個以陣列為引數的呼叫方法。並且如果我們定義一個void f2(String[] s)
的方法會提示重複
2 初始化問題
2.1 初始化順序
先說下java
物件初始化步驟:
本類
:靜態變數->靜態初始化塊->變數->初始化塊->建構函式繼承類
:父類靜態變數->父類靜態初始化塊->子類靜態變數->子類靜態初始化塊->父類變數->父類初始化塊->父類建構函式->子類變數->子類初始化塊->子類建構函式
2.2 子類繼承父類
預設建構函式的問題:假如base
類是父類,derived
類是子類,首先要
說明的是由於先有父類後有子類,所以生成子類之前要首先有父類。
class
是由class
的建構函式constructor
產生的,每一個class
都有
建構函式,如果你在編寫自己的class
時沒有編寫任何建構函式,那麼
編譯器為你自動產生一個預設default
建構函式。這個default建構函式
實質是空的,其中不包含任何程式碼。但是一牽扯到繼承,它的問題就出現
了。
如果父類base class
只有預設建構函式,也就是編譯器自動為你產生的。
而子類中也只有預設建構函式,那麼不會產生任何問題,因為當你試圖產生
一個子類的例項時,首先要執行子類的建構函式,但是由於子類繼承父類,
所以子類的預設建構函式自動呼叫父類的預設建構函式。先產生父類的例項,然後再產生子類的例項。
class base{
}
class derived extends base{
public static void main(String[] args){
derived d=new derived();
}
}
下面我自己顯式地加上了預設建構函式:
class base{
base(){
System.out.println("base constructor");
}
}
class derived extends base{
derived(){
System.out.println("derived constructor");
}
public static void main(String[] args){
derived d=new derived();
}
}
執行結果如下:說明了先產生 base class
然後是derived class
base constructor
derived constructor
我要說明的問題出在如果base class
有多個constructor
而derived class
也有多個constructor
,這時子類中的建構函式預設
呼叫那個父類的建構函式呢?答案是呼叫父類的預設建構函式。
但是不是編譯器自動為你生成的那個預設建構函式而是你自己顯式地
寫出來的預設建構函式。
class base{
base(){
System.out.println("base constructor");
}
base(int i){
System.out.println("base constructor int i");
}
}
class derived extends base{
derived(){
System.out.println("derived constructor");
}
derived(int i){
System.out.println("derived constructor int i");
}
public static void main(String[] args){
derived d=new derived();
derived t=new derived(9);
}
}
執行結果
base constructor
derived constructor
base constructor
derived constructor int i
如果將base
類的建構函式註釋掉,則出錯。
class base{
// base(){
// System.out.println("base constructor");
// }
base(int i){
System.out.println("base constructor int i");
}
}
class derived extends base{
derived(){
System.out.println("derived constructor");
}
derived(int i){
System.out.println("derived constructor int i");
}
public static void main(String[] args){
derived d=new derived();
derived t=new derived(9);
}
}
執行結果
derived.java:10: cannot resolve symbol
symbol : constructor base ()
location: class base
derived(){
^
derived.java:13: cannot resolve symbol
symbol : constructor base ()
location: class base
derived(int i){
2 errors
說明子類中的建構函式找不到顯式寫出的父類中的預設建構函式,所以出錯。
2.3 保護建構函式
那麼如果你不想子類的建構函式呼叫你顯式寫出的父類中的預設
建構函式怎麼辦呢?
如下例:
class base{
// base(){
// System.out.println("base constructor");
// }
base(int i){
System.out.println("base constructor int i");
}
}
class derived extends base{
derived(){
super(8);
System.out.println("derived constructor");
}
derived(int i){
super(i);
System.out.println("derived constructor int i");
}
public static void main(String[] args){
derived d=new derived();
derived t=new derived(9);
}
}
執行結果
base constructor int i
derived constructor
base constructor int i
derived constructor int i
super(i)
表示父類的建構函式base(i)
請大家注意
一個是super(i)
一個是super(8)
結論:子類如果有多個建構函式的時候,父類要麼沒有建構函式,讓編譯器自動產生,那麼在執行子類建構函式之前先執行編譯器自動產生的父類的預設建構函式;要麼至少要有一個顯式的預設建構函式可以讓子類的建構函式呼叫