天天看點

Java:初始化類、變量、程式塊加載探讨

<b>1.</b><b>基本類型資料的初始值</b>

InitialValues.java

<b>public</b> <b>class</b> InitialValues {

    <b>boolean</b> t;

    <b>char</b> c;

    <b>byte</b> b;

    <b>short</b> s;

    <b>int</b> i;

    <b>long</b> l;

    <b>float</b> f;

    <b>double</b> d;

    <b>void</b> print(String s) {

       System.out.println(s);

    }

    <b>void</b> printInitialValues() {

       print("boolean  " + t);

       print("char  " + c);

       print("byte  " + b);

       print("short  " + s);

       print("int  " + i);

       print("long  " + l);

       print("float  " + f);

       print("double  " + d);

    <b>public</b> <b>static</b> <b>void</b> main(String[] args) {

       InitialValues iv = <b>new</b> InitialValues();

       iv.printInitialValues();

}

結果:

boolean  false

char  _

byte  0

short  0

int  0

long  0

float  0.0

double  0.0

<b>2.</b><b>變量初始化</b>

在類的内部,變量定義的先後順序決定了初始化的順序。即使變量定義散布于方法定義之間,它們仍舊在任何方法(包括構造器)被調用之前得到初始化。看下面的代碼:

OrderOfInitialzation.java(執行順序在代碼中已标出,按類标注,羅馬字母标注主類中執行順序。)

<b>class</b> Tag {

    Tag(<b>int</b> marker) {

       System.out.println("Tag(" + marker + ")");

<b>class</b> Card {

    Tag t1 = <b>new</b> Tag(1);// Ⅰ①

    Card() {

       System.out.println("Card()");// Ⅰ④

       t3 = <b>new</b> Tag(33);// Ⅰ⑤

    Tag t2 = <b>new</b> Tag(2);// Ⅰ②

    <b>void</b> f() {

       System.out.println("f()");// Ⅱ⑥

    Tag t3 = <b>new</b> Tag(3);// Ⅰ③

<b>public</b> <b>class</b> OrderOfInitialzation {

       Card t = <b>new</b> Card();// Ⅰ

       t.f();// Ⅱ

Tag(1)

Tag(2)

Tag(3)

Card()

Tag(33)

f()

<b>3.</b><b>靜态資料初始化</b>

看下面的代碼:

StaticInitialization .java

<b>class</b> Bowl {

    Bowl(<b>int</b> marker) {

       System.out.println("Bowl(" + marker + ")");

    <b>void</b> f(<b>int</b> marker) {

       System.out.println("f(" + marker + ")");

<b>class</b> Table {

    <b>static</b> Bowl b1 = <b>new</b> Bowl(1);// Ⅰ①

    Table() {

       System.out.println("Table()");// Ⅰ③

       b2.f(1);// Ⅰ④

    <b>void</b> f2(<b>int</b> marker) {

       System.out.println("f2(" + marker + ")");

    <b>static</b> Bowl b2 = <b>new</b> Bowl(2);// Ⅰ②

<b>class</b> Cupboard {

    Bowl b3 = <b>new</b> Bowl(3);// Ⅱ③,Ⅳ①,Ⅵ①

    <b>static</b> Bowl b4 = <b>new</b> Bowl(4);// Ⅱ①

    Cupboard() {

       System.out.println("Cupboard");// Ⅱ④,Ⅳ②,Ⅵ②

       b4.f(2);// Ⅱ⑤,Ⅳ③,Ⅵ③

    <b>void</b> f3(<b>int</b> marker) {

       System.out.println("f3(" + marker + ")");

    <b>static</b> Bowl b5 = <b>new</b> Bowl(5);// Ⅱ②

<b>public</b> <b>class</b> StaticInitialization {

       System.out.println("Creating new Cupboard() in main");// Ⅲ

       <b>new</b> Cupboard();// Ⅳ

       System.out.println("Creating new Cupboard() in main");// Ⅴ

       <b>new</b> Cupboard();// Ⅵ

       t2.f2(1);// Ⅶ

       t3.f3(1);// Ⅷ

    <b>static</b> Table t2 = <b>new</b> Table();// Ⅰ

    <b>static</b> Cupboard t3 = <b>new</b> Cupboard();// Ⅱ

Bowl(1)

Bowl(2)

Table()

f(1)

Bowl(4)

Bowl(5)

Bowl(3)

Cupboard

f(2)

Creating new Cupboard() in main

f2(1)

f3(1)

由輸出可見,靜态初始化隻有在必要時刻才會進行。如果不建立Table 對象,也不引用Table.b1或Table.b2,那麼靜态的Bowl b1 和b2 永遠都不會被建立。隻有在第一個Table 對象被建立(或者第一次通路靜态資料)的時候,它們才會被初始化。此後,靜态對象不會再次被初始化。

初始化的順序是先“靜态”,(如果它們尚未因前面的對象建立過程而被初始化),後“非靜态”。從輸出結果中可以觀察到這一點。

<b>4.</b><b>靜态塊的初始化</b>

Java 允許你将多個靜态初始化動作組織成一個特殊的“靜态子句”(有時也叫作“靜态塊”)。與其他靜态初始化動作一樣,這段代碼僅執行一次:當你首次生成這個類的一個對象時,或者首次通路屬于那個類的一個靜态成員時(即便從未生成過那個類的對象)。看下面的代碼:

<b>class</b> Cup {

    Cup(<b>int</b> marker) {

       System.out.println("Cup(" + marker + ")");

<b>class</b> Cups {

    <b>static</b> Cup c1;

    <b>static</b> Cup c2;

    <b>static</b> {

       c1 = <b>new</b> Cup(1);

       c2 = <b>new</b> Cup(2);

    Cups() {

       System.out.println("Cups()");

<b>public</b> <b>class</b> ExpilicitStatic {

       System.out.println("Inside main()");

       Cups.c1.f(99);// (1)

    // static Cups x=new Cups();//(2)

    // static Cups y=new Cups();//(2)

Inside main()

Cup(1)

Cup(2)

f(99)

無論是通過标為(1)的那行程式通路靜态的 c1對象,還是把(1)注釋掉,讓它去運作标為(2) 的那行,Cups 的靜态初始化動作都會得到執行。如果把(1)和(2)同時注釋掉,Cups 的靜态初始化動作就不會進行。此外,激活一行還是兩行(2)代碼都無關緊要,靜态初始化動作隻進行一次。

<b>5.</b><b>非靜态執行個體初始化</b>

<b>class</b> Mug {

    Mug(<b>int</b> marker) {

       System.out.println("Mug(" + marker + ")");

<b>public</b> <b>class</b> Mugs {

    Mug c1;

    Mug c2;

    {

       c1 = <b>new</b> Mug(1);

       c2 = <b>new</b> Mug(2);

       System.out.println("c1&amp;c2 initialized");

    Mugs() {

       System.out.println("Mugs()");

       <b>new</b> Mugs();

       System.out.println("===new Mugs again===");

Mug(1)

Mug(2)

c1&amp;c2 initialized

Mugs()

===new Mugs again===

從結果可以看到,非靜态的代碼塊被執行了2次。是以隻要執行個體化一個類,該類中的非靜态代碼塊就會被執行一次。

<b>6.</b><b>數組初始化</b>

注意區分基本類型資料與類資料的初始化。看以下代碼:

<b>int</b>[] a; 

a = <b>new</b> <b>int</b>[rand.nextInt(20)];

Integer[] a = <b>new</b> Integer[rand.nextInt(20)]; 

<b>for</b>(<b>int</b> i = 0; i &lt; a.length; i++) { 

     a[i] = <b>new</b> Integer(rand.nextInt(500)); 

對于類資料類型的初始化,每個數組子成員都要重新new一下。

<b>7.</b><b>涉及繼承關系的初始化</b>

當建立一個導出類的對象時,該對象包含了一個基類的子對象。看下面代碼:

<b>class</b> Art {

    Art() {

       System.out.println("Art constructor");

<b>class</b> Drawing <b>extends</b> Art {

    Drawing() {

       System.out.println("Drawing constructor");

<b>public</b> <b>class</b> Cartoon <b>extends</b> Drawing {

    <b>public</b> Cartoon() {

       System.out.println("Cartoon constructor");

       <b>new</b> Cartoon();

Art constructor

Drawing constructor

Cartoon constructor

可以發現,建構過程是從基類“向外”擴散的,是以基類在導出類構造器可以通路它之前,就已經完成了初始化。

如果類沒有預設的參數,或者想調用一個帶參數的基類構造器,就必須用關鍵字super顯示地調用基類構造器的語句,并且配以适當的參數清單。看下面代碼:

<b>class</b> Game {

    Game(<b>int</b> i) {

       System.out.println("Game constructor");

<b>class</b> BoardGame <b>extends</b> Game {

    BoardGame(<b>int</b> i) {

       <b>super</b>(i);

       System.out.println("BoardGame constructor");

<b>public</b> <b>class</b> Chess <b>extends</b> BoardGame {

    Chess() {

       <b>super</b>(11);

       System.out.println("Chess constructor");

       <b>new</b> Chess();

Game constructor

BoardGame constructor

Chess constructor

如果不在BoardGame()中調用基類構造器,編譯器将無法找到符合Game()形式的構造器。而且,調用基類構造器必須是你在導出類構造器中要做的第一件事。

<b>8.</b><b>構造器與多态</b>

8.1構造器的調用順序

基類的構造器總是在導出類的構造過程中被調用的,而且按照繼承層次逐漸向上連結,以使每個基類的構造器都能得到調用。這樣做是有意義的,因為構造器具有一項特殊的任務:檢查對象是否被正确地構造。導出類隻能通路它自己的成員,不能通路基類中的成員(基類成員通常是private類型)。隻有基類的構造器才具有恰當的知識和權限來對自己的元素進行初始化。是以,必須令所有構造器都得到調用,否則就不可能正确構造完整對象。這正是編譯器為什麼要強制每個導出類部分都必須調用構造器的原因。

8.2構造器内部的多态方法的行為

看下面代碼:

<b>abstract</b> <b>class</b> Glyph {

    <b>abstract</b> <b>void</b> draw();

    Glyph() {

       System.out.println("Glyph() before draw()");

       draw();

       System.out.println("Glyph() after draw()");

<b>class</b> RoundGlyph <b>extends</b> Glyph {

    <b>private</b> <b>int</b> radius = 1;

    RoundGlyph(<b>int</b> r) {

       radius = r;

       System.out.println("RoundGlyph.RoundGlyph(),radius=" + radius);

    <b>void</b> draw() {

       System.out.println("RoundGlyph.draw(),radius=" + radius);

<b>public</b> <b>class</b> PolyConstructors {

       <b>new</b> RoundGlyph(5);

Glyph() before draw()

RoundGlyph.draw(),radius=0

Glyph() after draw()

RoundGlyph.RoundGlyph(),radius=5

在Glyph中,draw()方法是抽象的,這樣設計是為了覆寫該方法。我們确實在RoungGlyph中強制覆寫了draw()。但是Glyph構造器會調用這個方法,結果導緻了對RoundGlyph.draw()的調用,這看起來似乎是我們的目的。但是如果看到輸出結果,我們會發現當Glyph的構造器調用draw()方法時,radius不是預設初始值1,而是0。

解決這個問題的關鍵是初始化的實際過程:

1)在其他任何事物發生之前,将配置設定給對象的存儲空間初始化成二進制零。

2)如前所述那樣調用基類構造器。此時,調用被覆寫後的draw()方法(要在調用RoundGlyph構造器之前調用),由于步驟1的緣故,我們此時會發現radius的值為0。

3)按照聲明的順序調用成員的初始化方法。

4)調用導出類的構造器主體。

<b>9.</b><b>初始化及類的加載</b>

看以下代碼:

<b>class</b> Insect {

    <b>private</b> <b>int</b> i = 9;

    <b>protected</b> <b>int</b> j, m;

    Insect() {

       System.out.println("i = " + i + ", j = " + j);

       j = 39;

    <b>private</b> <b>static</b> <b>int</b> x1 = print("static Insect.x1 initialized");

    <b>static</b> <b>int</b> print(String s) {

       <b>return</b> 47;

    Tag t1 = <b>new</b> Tag(1);

<b>public</b> <b>class</b> Beetle <b>extends</b> Insect {

    <b>private</b> <b>int</b> k = print("Beetle.k initialized");

    <b>public</b> Beetle() {

       System.out.println("k = " + k);

       System.out.println("j = " + j);

       System.out.println("m = " + m);

    <b>private</b> <b>static</b> <b>int</b> x2 = print("static Beetle.x2 initialized");

       System.out.println("Beetle constructor");

       Beetle b = <b>new</b> Beetle();

    Tag t2 = <b>new</b> Tag(2);

static Insect.x1 initialized

static Beetle.x2 initialized

Beetle constructor

i = 9, j = 0

Beetle.k initialized

k = 47

j = 39

m = 0

你在Beetle 上運作Java 時,所發生的第一件事情就是你試圖通路Beetle.main( ) (一個static 方法),于是加載器開始啟動并找出 Beetle 類被編譯的程式代碼(它被編譯到了一個名為Beetle .class 的檔案之中)。在對它進行加載的過程中,編譯器注意到它有一個基類(這是由關鍵字 extends  告知的),于是它繼續進行加載。不管你是否打算産生一個該基類的對象,這都要發生。

如果該基類還有其自身的基類,那麼第二個基類就會被加載,如此類推。接下來,根基類中的靜态初始化(在此例中為Insect)即會被執行,然後是下一個導出類,以此類推。這種方式很重要,因為導出類的靜态初始化可能會依賴于基類成員能否被正确初始化的。

至此為止,必要的類都已加載完畢(靜态變量和靜态塊),對象就可以被建立了。

首先,對象中所有的原始類型都會被設為預設值,對象引用被設為null——這是通過将對象記憶體設為二進制零值而一舉生成的。然後,基類的構造器會被調用。在本例中,它是被自動調用的。但你也可以用super 來指定對基類構造器的調用。基類構造器和導出類的構造器一樣,以相同的順序來經曆相同的過程,即向上尋找基類構造器。在基類構造器完成之後(即根部構造器找到之後),執行個體變量(instance variables )按其次序被初始化(注意觀察代碼中的Tag())。最後,構造器的其餘部分被執行。

本文轉自zhangjunhd51CTO部落格,原文連結:http://blog.51cto.com/zhangjunhd/20927,如需轉載請自行聯系原作者