轉載http://zangxt.iteye.com/blog/472238
關於java的構造方法有幾個簡單的問題:
1.構造方法有返回值嗎?
沒有。構造方法沒有任何返回類型,也不允許是void。比如:
- public class Test {
- //這不是構造函數!
- public void Test() {
- System.out.println("void Test()");
- }
- public static void main(String[] args) {
- Test test = new Test();
- test.Test();
- }
- }
這裏我們定義了一個返回類型爲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的。因此,"構造器實際上也是靜態方法"這點很好否定。看下面例子:
- public class Test {
- public Test() {
- this.test2();
- }
- public static void test(){
- this.test2();
- }
- public static void test2(){
- }
- }
test方法編譯錯誤,因爲靜態方法中不能使用非靜態的this,而構造方法使用this是沒有問題的。
如果有C++經驗的話,可以類比一下。C++裏的new操作符有兩個作用,調用operator new()來分配內存,然後調用構造函數來完成初始化。
而這裏的operator new()是隱式靜態的。參考《C++程序設計語言(特別版)》中文版的374頁:
比如這個例子:
- class Employee{
- //...
- public:
- //....
- void* operator new(size_t);
- void operator delete(void* ,size_t);
- }
成員operator new()和operator delete()默認爲static成員,因爲它們沒有this指針,也不會修改任何對象。它們將提供一些存儲,供構造函數去初始化,而後由析構函數去清理。
類比可知,靜態的是負責分配內存的工具,而不是構造函數。 不知道《Thinking In Java》的作者是不是把這點弄混了?
3.父類的構造方法中調用被子類重寫的方法有多態現象。
這句話很繞,直接看例子:
- class Father{
- private int i = 5;
- public Father() {
- System.out.println("Father's i is " + this.i);
- test();
- }
- public void test(){
- System.out.println(this.i);
- }
- }
- class Son extends Father{
- private int i = 55;
- public Son() {
- System.out.println("Son's i is " + this.i);
- }
- @Override
- public void test() {
- System.out.println(this.i);
- }
- }
- public class Test {
- public static void main(String[] args) {
- new Son();
- }
- }
結果是:
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程序員天生就有相反的期待嗎?