java 構造方法詳解 及 new 關鍵字的租用

轉載http://zangxt.iteye.com/blog/472238

關於java的構造方法有幾個簡單的問題:

1.構造方法有返回值嗎?

     沒有。構造方法沒有任何返回類型,也不允許是void。比如:

Java代碼  收藏代碼
  1. public class Test {   
  2.  //這不是構造函數!   
  3. public void Test() {   
  4. System.out.println("void Test()");   
  5. }   
  6. public static void main(String[] args) {   
  7. Test test = new Test();   
  8. test.Test();   
  9. }   
  10. }   

 

 

 

 

      這裏我們定義了一個返回類型爲void的Test()方法,有了返回類型void,它就不是構造方法了。

 

     Test test = new Test();

     有人用上面的表達式來說明構造方法返回對象引用,這是明顯錯誤的。new關鍵字有兩個作用。一是分配內存,

創建對象。二是調用構造方法,完成對象的初始化工作。完成這兩步之後,纔算創建了一個完整的Java對象。我們

可以用Test test = new Test();的反編譯代碼來說明這個問題:

0:    new    #5; //class Test

3:    dup

4:    invokespecial    #6; //Method "":()V

7:    astore_1

      原表達式被編譯成四條bytecode指令。

new指令負責根據參數分配內存並創建Test對象,然後將新創建對象的引用置於棧頂。

dup指令複製棧頂的內容,記住,此時棧最頂端的兩個單元都是新創建對象的引用。

接着是調用初始化方法,該方法是由構造方法編譯而來,棧頂的引用作爲此方法的參數消耗了。通過調用初始化方法完成

對象的創建過程。這裏注意一下初始化方法Method "":()V,它是void類型的。

最後的astore_1指令將棧頂的對象引用賦給局部變量(前面說了,dup之後棧頂兩個引用,一個給初始化方法喫掉了,一個留給astore_1操作用),也就是執行賦值操作。

     因此,得到的引用是new指令的結果,不是構造方法的返回值。

     有一點需要注意:new指令創建對象,同時使對象的各個字段得到其默認值,比如整數爲0,浮點數爲0.0,引用爲null,boolean爲false等。也就是說在構造方法執行之前,各個字段都有默認值了。這一點我們在第三條繼續說明。

     通過上面說明,我們明確了構造方法的職能(初始化new指令創建的對象,得到一個狀態合法的對象,完成對象的

創建過程)。任何類都有構造方法,但是new指令只能創建非抽象類的對象。理解了這一點,也就不要再問"抽象類也有構造方法,爲什麼不能創建對象"之類的問題了。

 

2.構造方法是靜態的?

      錯誤。

      這是《Thinking In Java》中的一個觀點。書裏有一段:

Even though it doesn't explicitly use the static keyword, the constructor is actually a static method. So the first time an object of type Dog is created, or the first time a static method or static field of class Dog is accessed, the Java interpreter must locate Dog.class, which it does by searching through the classpath.

《java編程思想》中文第四版96頁:

總結一下對象的創建過程,假設有個名爲Dog的類:

1.即使沒有顯示地使用static關鍵字,構造器實際上也是靜態方法。因此,當首次創建類型爲Dog的對象時(構造器可以看成

是靜態方法),或者Dog類的靜態方法/靜態域首次被訪問時,Java解釋器必須查找類路徑,以定位Dog.class文件。

 

     這裏我並沒有看明白作者爲什麼說構造器實際上是靜態方法。但是我們知道,靜態方法是不能使用this的。因此,"構造器實際上也是靜態方法"這點很好否定。看下面例子:

 

Java代碼  收藏代碼
  1. public class Test {   
  2. public Test() {   
  3. this.test2();   
  4. }   
  5. public static void test(){   
  6. this.test2();   
  7. }   
  8. public static void test2(){   
  9. }  
  10. }   

 

 

   test方法編譯錯誤,因爲靜態方法中不能使用非靜態的this,而構造方法使用this是沒有問題的。

 

      如果有C++經驗的話,可以類比一下。C++裏的new操作符有兩個作用,調用operator new()來分配內存,然後調用構造函數來完成初始化。

     而這裏的operator new()是隱式靜態的。參考《C++程序設計語言(特別版)》中文版的374頁:

比如這個例子:

Cpp代碼  收藏代碼
  1. class Employee{  
  2. //...   
  3. public:   
  4. //....   
  5. void* operator new(size_t);   
  6. void operator delete(void* ,size_t);   
  7. }   

 

     成員operator new()和operator delete()默認爲static成員,因爲它們沒有this指針,也不會修改任何對象。它們將提供一些存儲,供構造函數去初始化,而後由析構函數去清理。

  類比可知,靜態的是負責分配內存的工具,而不是構造函數。 不知道《Thinking In Java》的作者是不是把這點弄混了?

 

3.父類的構造方法中調用被子類重寫的方法有多態現象。

     這句話很繞,直接看例子:

Java代碼  收藏代碼
  1. class Father{     
  2.     private int i = 5;     
  3.     public Father() {     
  4.         System.out.println("Father's i is " + this.i);     
  5.         test();     
  6.     }     
  7.     public void test(){     
  8.         System.out.println(this.i);     
  9.     }     
  10. }     
  11.     
  12. class Son extends Father{     
  13.     private int i = 55;     
  14.     
  15.     public Son() {     
  16.         System.out.println("Son's i is " + this.i);     
  17.     }     
  18.     
  19.     @Override    
  20.     public void test() {     
  21.         System.out.println(this.i);     
  22.     }     
  23.     
  24. }     
  25. public class Test {     
  26.     public static void main(String[] args) {     
  27.         new Son();     
  28.     }     
  29. }   

     

 

 

 結果是:

Father's i is 5
0
Son's i is 55

     結合第一點,構造方法調用之前,首先是new指令創建了一個對象,並將各個成員初始化爲其默認值。下面看構造方法的調用過程。

     子類構造方法會調用父類構造方法,父類構造方法首先打印Father's i is 5。然後調用test()方法,注意,我們創建的是Son類的對象,所以test()方法調用的是Son類定義的test()方法,也就是說發生了多態。我們再去看Son類中test方法的實現,就是簡單的輸出this.i,爲什麼是0呢,別忘了我們還沒有執行子類的構造方法啊,所以此時子類的i還是new指令初始化得到的0。好,test()方法執行完了,總算回到子類構造方法繼續執行,先把i賦值爲55,下面的輸出語句Son's i is 55也就不難理解了。

    在構造方法中調用方法要特別注意這種多態現象。

 

    這種現象和c++裏的現象是不同的。在C++中,如果在父類的構造函數中調用虛方法的話,調用的是父類定義的版本,不會發生多態現象。但一個有趣的現象是,C++的經典書籍和Java的經典書籍竟然都建議不要在構造方法裏面調用多態方法,因爲現象並不是你期待的!這就奇怪了,難道C++程序員和Java程序員天生就有相反的期待嗎?


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