我們有必要對整個初始化過程有所認識,其中包括繼承,對這個過程中發生的事情有一個整體性的概念。請觀察下述代碼:
//: Beetle.java // The full process of initialization. class Insect { int i = 9; int j; static int x1 = prt("static Insect.x1 initialized");//注意這里是static字段 Insect() { prt("i = " + i + ", j = " + j); j = 39; } static int prt(String s) { System.out.println(s); return 47; } } public class Beetle extends Insect { int k = prt("Beetle.k initialized"); static int x2 = prt("static Beetle.x2 initialized");//注意這里是static字段 Beetle() { prt("k = " + k); prt("j = " + j); } static int prt(String s) { System.out.println(s); return 63; } public static void main(String[] args) { prt("Beetle constructor"); Beetle b = new Beetle(); } }
該程序的輸出如下:
static Insect.x initialized
static Beetle.x initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 63
j = 39
對Beetle 運行Java 時,發生的第一件事情是裝載程序到外面找到那個類。
在裝載過程中,裝載程序注意它有一個基礎類(即extends 關鍵字要表達的意思),所以隨之將其載入。
無論是否准備生成那個基礎類的一個對象,這個過程都會發生(請試着將對象的創建代碼當作注釋標注出來,自己去證實)。
若基礎類含有另一個基礎類,則另一個基礎類隨即也會載入,以此類推。
接下來,會在根基礎類(此時是Insect)執行static 初始化,再在下一個衍生類執行,以此類推。
保證這個順序是非常關鍵的,因為衍生類的初始化可能要依賴於對基礎類成員的正確初始化。
此時,必要的類已全部裝載完畢,所以能夠創建對象。
首先,這個對象中的所有基本數據類型都會設成它們的默認值,而將對象句柄設為null 。隨后會調用基礎類構建器。
在這種情況下,調用是自動進行的。但也完全可以用super 來自行指定構建器調用(就象在Beetle()構建器中的第一個操作一樣)。
基礎類的構建采用與衍生類構建器完全相同的處理過程。基礎順構建器完成以后,實例變量會按本來的順序得以初始化。
最后,執行構建器剩余的主體部分。
構造器順序:
class AA{ AA(){System.out.println("AA");} } class BB{ BB(){System.out.println("BB");} } public class Test extends BB{ private AA aa = new AA();//組合 Test(){System.out.println("Test");} public static void main(String[] args) { Test ee= new Test(); } }
//輸出:
BB
AA
Test
擴展例子:
package com.com; class AA{ AA(){System.out.println("AA");} } class BB{ static int i = prt("BB static"); BB(){System.out.println("BB");} static int prt(String s) { System.out.println(s); return 47; } } public class TestMain extends BB{ private AA aa = new AA(); TestMain(){ System.out.println("Test"); } public static void main(String[] args) { System.out.println("TestMain:main"); TestMain ee= new TestMain(); } }
//輸出:
BB static
TestMain:main
BB
AA
Test
結論:復雜對象構造器順序如下:
(1)在其他任何事物發生之前,將分配給對象的存儲空間初始化為2進制的0,並且加載類(當然就包括初始化類的static成員).
(2)調用基類構造器(並且遞歸調用)
(3)按聲明順序調用成員初始化方法(這里就是說Test類組合部分AA的初始化部分)
(4)調用派生類構造器主體。
動態綁定:
JAVA中除了static和final方法(private方法屬於final方法),其他所有的方法都是動態綁定的。
我們要知道private方法被自動認為是final方法,而且對派生類是屏蔽的,也就是說如果派生類重寫了改方法是一個新的方法,所以說只有非private方法才可以被覆蓋。
繼承和清理:
一般我們是不必擔心對象清理的問題,因為會留給垃圾回收器來處理。
如果我們一定要自己清理對象,那么就必須自己維護好清理順序。主要有這樣幾個方面需要注意:
1. 要為新類定義一個清理函數(比如dispose),我們自己維護,派生類復寫基類的該方法
2. 派生類在該方法的實現最后必須調用基類的dispose方法
3.還必須注意成員對象銷毀的順序必須要和聲明的順序相反(因為成員初始化時按聲明的順序來構造的)
實際上也就是和C++的析構函數類似。
構造器內部的多態行為:(在構造器內部調用多態方法)
class AA{ void draw(){System.out.println("AA:draw");} AA() { System.out.println("before:draw"); draw(); System.out.println("after:draw"); } } class BB extends AA{ private int rad = 1; BB(int i) { rad = i; System.out.println("BB:rad = "+rad); } void draw(){System.out.println("BB:draw--rad = " + rad);} } public class Test{ public static void main(String[] str) { new BB(5); } }
//輸出:
before:draw
BB:draw--rad = 0 //這里不是1,因為會先將分配給對象的存儲空間初始化為2進制的0.
after:draw
BB:rad = 5
顯然調用draw函數的多態性了。上面的輸出結果很詭異,也給我們有了很好的提示。
總結:
編寫構造器法則:
用盡可能簡單的方法使對象進入正常狀態,如果可以的話,避免調用其他方法。在構造中唯一能夠安全調用的方法就是基類中的final方法(private方法也屬於final方法),因為final方法不可能存在多態的可能。
接口基本知識:
interface Intest
{
int VALUE = 5;//接口中字段默認就是static和final的
void play();//默認是public
void adjust();
}