Thinking In Java筆記(第五章 初始化與清理(三))

來源:互聯網
上載者:User

標籤:初始化   java初始化   enum   靜態語句塊   初始化順序   

第五章 初始化與清理5.6 成員初始化

    Java儘力保證:所有變數在使用前都能得到恰當的初始化。對於方法的局部變數,Java以編譯錯誤的形式來保證。如下:

void f() {    int i;    i++; //Error:i not initialized}

    會得到一條錯誤的訊息,提示i可能沒有初始化。編譯器可以給i賦初值,但是並沒有這麼做,因為沒有初始化是程式員的疏忽,為了保證程式的穩定性,能協助程式員找出程式的缺陷所在。

    但如果是類的資料成員是基本類型,類的每個基本類型成員變數都會保證有一個初值。如下:

public class Test {    boolean t;    char c;    byte b;    short s;    int i;    long l;    float f;    double d;    InitialValues reference;}

    上面的一系列數值分別為:false, (空白), 0, 0, 0, 0, 0.0, 0.0, null。char值為0,所以顯示為空白。

5.6.1 指定初始化

    有一種很直接的方法給某個變數賦初值,就是在定義類成員變數的地方直接為其賦值,C++中不允許。對初始化非基本類型的對象也可以,例如下面的A類的對象,這樣類Test的每個對象都會具有相同的初始值。如下:

class A{}public class Test {    boolean bool = true;    char ch = ‘x‘;    int i = 99;    A a = new A();}
5.7 構造器初始化

    還可以用構造器來進行初始化。在運行時刻,可以調用方法或執行某些動作來確定初值,這給編程帶來了更大的靈活性。但是要記住,無法組織自動初始化的進行,也就是前面提到的編譯器自動賦值,這個工作在構造器被調用之前就發生。例如:

public class Test {     int i;    Test() {i = 7;} }

    i的值首先被置為0,再被賦值為7。

5.7.1 初始化順序

    在類的內部,變數定義的先後順序決定了初始化的順序。即使變數定義散布與方法定義之間,它們人就會在任何方法(包括構造器)被調用之前得到初始化。例如:

class Window {    Window(int maker) {        System.out.println("Window(" + maker + ")");    }}class House {    Window w1 = new Window(1);    House() {        System.out.println("House");        w3 = new Window(33);    }    Window w2 = new WIndow(2);    void f() {        System.out.println("f()");    Window w3 = new Window(3);    }}public class Test {    public static void main(String[] args) {        House h = new House();        h.f();    }   }

    結果為Window(1) Window(2) Window(3) House() Window(33) f()

5.7.2 待用資料的初始化

    無論建立多少個對象,待用資料都至佔用一份儲存地區。static關鍵字不能應用於局部變數。看下面的例子:

class Bowl {    Bowl(int marker) {        System.out.println("Bowl(" + marker + ")");    }    void f1(int marker) {        System.out.println("f1(" + marker + ")");    }}class Table {    static Bowl bowl1 = new Bowl(1);    Table() {        System.out.println("Table()");        bowl2.f1(1);    void f2(int marker) {        System.out.println("f2(" + marker + ")");    }    static Bowl bowl2 = new Bowl(2);    }class Cupboard {    Bowl bowl3 = new Bowl(3);    static Bowl bowl4 = new Bowl(4);    Cupboard() {        System.out.println("Cupboard()");        bowl4.f1(2);    }    void f3(int marker) {        System.out.println("f3(" + marker + ")");    }    static Bowl bowl5 = new Bowl(5);}}public class Test {    public static void main(String[] args){        System.out.println("Creating new Cupboard() in main");        new Cupboard();        Syste.out.println("Creating new Cupboard() in main");        new Cupboard();        table.f2(1);        cupboard.f3(1);    }    static Table table = new Table();    static Cupboard cupboard = new Cupbpard();}

    輸出的結果依次為:

Bowl(1) Bowl(2) Table() f1(1) Bowl(4)Bowl(5)Bowl(3) Cupboard()f1(2) Creating new Cupboard() in main Bowl(3)     Cupboard() f1(2) Creating new Cupboard() in main Bowl(3)Cupboard()f1(2)f2(1)f3(1)

    靜態初始化只有在必要的時候才會進行,只有在第一個類對象被建立(或第一次訪問待用資料)的時候,待用資料才會被初始化。之後,無論怎麼建立對象,都不會再次被初始化。

    初始化的順序是先靜態對象(如果它們尚未被初始化),而後是”非靜態對象”。

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

  1. 即使沒有顯式的使用static關鍵字,構造器實際上也是靜態方法。因此,當首次建立類型為Dog對象時(構造器可以看成靜態方法),或者Dog類的靜態方法、靜態域首次被訪問的時候,Java解譯器必須尋找類的路徑,以定位Dog.class。
  2. 然後載入Dog.class,有關靜態初始化的所有動作都會被執行,因此,靜態初始化只在Class對象首次載入的時候進行一次。
  3. 當用new建立Dog的對象時候,首先在堆上給Dog對象分配足夠的儲存空間。
  4. 清零所分配的儲存空間,這就自動的將Dog對象中所有的基礎資料型別 (Elementary Data Type)設定成了預設值(對數字來說就是0,對boolean和char類型的來說也類似),而引用則都被設定成了null。
  5. 執行所有出現於欄位定時的初始化動作。
  6. 執行構造器方法。
5.7.3 顯示的靜態初始化

    Java允許多個靜態初始化動作組織成一個特殊的”靜態子句”(有時也叫做”靜態塊”)。就像下面這樣:

public class Spoon {    static int i;    static {        i = 47;    }}

    和其他靜態初始化動作一樣,這段代碼只執行一次:當首次產生這個類的對象時,或首次訪問屬於這個類的靜態成員資料的時候執行。例如:

class Cup {    Cup(int marker) {        System.out.println("Cup(" + marker + ")");    }    void f(int marker) {        System.out.println("f(" + marker + ")");    }}class Cups {    static Cup cup1;    static Cup cup2;    static {        cup1 = new Cup(1);        cup2 = new Cup(2);    }    Cups() {        System.out.println("Cups()");    }}public class Test {    public static void main(String[] args) {        System.out.println("Inside main()");        Cups.cup1.f(99);  //  (1)    }    //static Cups cups1 = new Cups(); //  (2)    //static Cups cup2 = new Cups();  //  (2)}

    上面的結果為:Inside main() Cup(1) Cup(2) f(99)

5.7.4 非靜態執行個體初始化

    Java中也有被成為執行個體初始化的類似文法,用來初始化每一個對象的非靜態變數。和靜態語句塊一樣的,只不過少了static。如果不建立類的執行個體,非靜態語句塊是不會被執行,只會觸碰static變數和語句塊。

下面用一個例子來總結下上述的順序:

class Cup {{    System.out.println("Block - Cup");}static int c;static {    c = 1;    System.out.println("Static Bolck - Cup");}    Cup(int marker) {        System.out.println("Construct - Cup(" + marker + ")");    }    void f(int marker) {        System.out.println("Function - f(" + marker + ")");    }}class Cups {    static {        cup1 = new Cup(1);        cup2 = new Cup(2);    }    static Cup cup1;    static Cup cup2;    {        System.out.println("Block - Cups");    }    Cups() {        System.out.println("Construct - Cups()");    }}public class JavaTest {    public static void main(String[] args) {        System.out.println("Inside main()");        Cups.cup1.f(99);  //  (1)    }}

    輸出的結果為:

Inside main()Static Bolck - CupBlock - CupConstruct - Cup(1)Block - CupConstruct - Cup(2)Function - f(99)

    從上面的結果可以看出,沒有建立Cups類的對象時,不會執行非靜態語句塊,也就是被{}包括起來的語句塊。在第一次建立類對象或者使用到類的靜態變數的時候,就會將.class檔案載入進來,初始化static變數,執行static{}語句塊。

5.8 數組初始化

    數組只是相同類型的、用一個標識符名稱封裝到一起的一個對象序列或基本類型資料序列。素組是通過方括弧下標操作符【】來定義和使用的。定義數組只需要在類型名後面加上一對方括弧:int[] a1;。方括弧也可以放在後面:int a1[];

    兩種格式的含義是一樣的,後面一種格式符合C和C++程式員的習慣。前面一種格式能更直觀的看出,其表明的類型是”一個int型數組”。

    編譯器不允許指定數組的大小,現在擁有的只是對數組的一個引用(你已經為該引用分配了足夠的儲存空間),而且也沒給數組對象本身分配任何空間。為了給數組建立相應的儲存空間,需要對數組進行初始化。數組的初始化代碼可以出現在代碼的任何地方,但也可以使用一種特殊的初始設定式,必須在建立數組的地方出現。這種特殊的初始化是由一對花括弧括起來的,儲存空間的分配(等價於使用new)將由編譯器負責。例如:

int[] a1 = {1,2,3,4,5};

    那麼為什麼還要在沒有數組的時候定義一個數組的引用呢?Java中可以將數組賦值給另一個數組,int[] a2;,在Java中可以將一個數組賦值給另一個數組,所以可以這樣:a2 = a1;直接複製一個引用。下面的例子:

public class ArrayTest {    public static void main(String[] args) {        int[] a1 = {1,2,3,4,5};        int[] a2;        a2 = a1;        for(int i = 0;i < a2.length; i++)            a2[i] = a2[i] + 1;        for(int i = 0;i < a1.length; i++)            System.out.println("a1[" + i + "]" + a[i]);    }}

    輸出為a1[0]=1 a1[1]=2 a1[2]=3 a1[3]=4 a1[4]=5

    所有數組(無論元素始對象還是基本類型)都有一個固有成員length,可以通過它獲得數組長度,但其不能直接被更改。和C與C++類似,Java數組計數從0開始,數組越界,C和C++默默接受,但Java直接出現執行階段錯誤。

    可以在編程時,通過new再數組裡面建立元素。儘管建立的是基本類型數組,new仍然可以工作(不能用new建立單個的基本類型資料)。

public class ArrayNew {    public static void main(String[] args) {        int[] a;        Random rand = new Random(47);        a = new int[rand.nextInt(20)];    }}

    如果建立了一個非基本類型的數組,那麼就是一個引用數組。以整型的封裝器類Integer為例:

public class Test {    public static void main(String[] args) {        Random rand = new Random(47);        Integer[] a = new Integer[rand.nextInt(20)];    }}

    這裡即便用new建立了數組之後,也只是一個引用數組,並且直到通過建立新的Integer對象,並把對象和引用串連起來,初始化才算結束。如果忘記了建立對象,並連結化物件和引用,那數組中就全是Null 參考,運行時會產生異常。

5.8.1 可變參數列表

    可變參數列表可用於參數個數或者類型未知的場合。例如void f(Object[] args)函數裡的參數。這種在Java SE5之前出現,然而再Java SE5中,添加入了新特性,可以使用新特性來定義可變參數列表了,下面的例子:

public class NewVarArgs {    static void printArray(Object... args) {        for(Object obj : args) {            System.out.println(obj + " ");        }    }    public static void main(String[] args) {        printArray(new Integer(47), new Float(3.14), new Double(11.11));        printArray(47, 3.14F, 11.11);        printArray("one", "two", "three");    }}

    有了可變參數,就不用顯示的編寫數組文法了,當指定參數的時候,編譯器實際上會去填充數組,最後得到的仍然是一個數組。

5.9 枚舉類型

    Java SE5中添加了一個看似很小的特性,即enum關鍵字,它使得我們在需要群組並使用枚舉集時可以很方便的處理。C和C++以及以其他很多語言已經擁有枚舉類型了,Java中枚舉類型功能比C/C++的功能更加完備。下面是簡單的樣本:

public enum A {    NOT, MILD, MEDIUM, HOT, FLAMING}

    這裡建立了一個名為A的枚舉類型,它具有5種值,由於枚舉類型的執行個體是常量,因此按照命名習慣通常用大寫字母表示(有多個字母用底線隔開)。

    為了使用enum,需要建立一個該類型的引用,並和某個執行個體串連起來。

public class Test {    public static void main(String[] args) {        A a = A.MEDUIM;        System.out.println(a);    }}

    在建立枚舉類型的時候,編譯器會自動添加一些特性。例如:

  • 會建立toString()方法,一邊可以很方便的顯示某個enum執行個體的名字。
  • 建立ordinal()方法,用來表示某個特定enum常量的聲明順序。
  • static values()方法,用來按照enum常量的聲明順序,產生由這些常量值構成的數組。

    例子如下:

public class EnumOrder {    public static void main(String[] args){        for(A a : A.values) {            System.out.println(s + ", oridinal " + a.ordinal());        }    }}輸出結果為:NOT, oridinal 0MILD, oridinal 1MEDIUM, oridinal 2HOT, oridinal 3FLAMING, oridinal 4

    enum的另一個特別實用的特性是能和switch語句一起使用。看下面的例子:

    enum Pet {    Cat,    Dog,    Bird}public class JavaTest {    Pet pet;    public JavaTest(Pet p) {        pet = p;    }    public void describe() {        switch(pet) {        case Cat :            System.out.println("The pet is Cat");            break;        case Dog :            System.out.println("The pet is Dog");            break;        case Bird :            System.out.println("The pet is Bird");            break;        }    }    public static void main(String[] args) {        Pet p1 = Pet.Bird;        JavaTest test = new JavaTest(p1);        test.describe();    }}結果為:The pet is Bird

Thinking In Java筆記(第五章 初始化與清理(三))

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.