JAVA繼承初始化過程


我們有必要對整個初始化過程有所認識,其中包括繼承,對這個過程中發生的事情有一個整體性的概念。請觀察下述代碼:

//: 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();
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM