Java編程思想 第5章 初始化與清理

Java編程思想 第5章 初始化與清理

標籤(空格分隔): JAVA學習


5.1 用構造器確保初始化

不接受任何參數的構造器叫做默認構造器,構造器的名稱必須與類名完全相同。

如果類中沒有構造器,編譯器會自動創建一個默認構造器。

如果已經定義了一個構造器(無論是否有參數),編譯器則不會自動創建默認構造器。

class Rock {
    Rock() {
        System.out.print("Rock ");
    }
}

class Rock2 {
    Rock2(int i) {
        System.out.print("Rock2 " + i + " ");
    }
}
public class SimpleConstructor {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Rock();
        }

        System.out.println("");

        for (int i = 0; i < 8; i++) {
            new Rock2(i);
        }
    }
}
Output:
Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock 
Rock2 0 Rock2 1 Rock2 2 Rock2 3 Rock2 4 Rock2 5 Rock2 6 Rock2 7 

5.2 方法重載

方法重載:方法名相同,形參不同。

class Tree {
    int height;
    Tree() {
        System.out.println("Planting a seedling");
        height = 0;
    }

    // 重載的構造器
    Tree(int initialHeight) {
        height = initialHeight;
        System.out.println("Creating new Tree that is " + height + " feet tall");
    }

    void info() {
        System.out.println("Tree is " + height + " feet tall");
    }

    // 重載的方法
    void info(String s) {
        System.out.println(s + ": Tree is " + height + " feet tall");
    }
}
public class Overloading {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Tree t = new Tree(i);
            t.info();
            t.info("overloaded method");
            System.out.println("");
        }
        System.out.println("");
        new Tree();
    }
}
/* Output:
Creating new Tree that is 0 feet tall
Tree is 0 feet tall
overloaded method: Tree is 0 feet tall

Creating new Tree that is 1 feet tall
Tree is 1 feet tall
overloaded method: Tree is 1 feet tall

Creating new Tree that is 2 feet tall
Tree is 2 feet tall
overloaded method: Tree is 2 feet tall

Creating new Tree that is 3 feet tall
Tree is 3 feet tall
overloaded method: Tree is 3 feet tall

Creating new Tree that is 4 feet tall
Tree is 4 feet tall
overloaded method: Tree is 4 feet tall


Planting a seedling
*/

5.2.1 區分重載方法

區分規則:每個重載的方法都必須有一個獨一無二的參數類型列表(參數順序不同也足以區分兩個方法,但會使代碼難以維護)。
注意:參數類型列表相同,根據返回值來區分重載方法是行不通的。

5.2.2 涉及基本類型的重載

  • 實參類型 < 重載方法聲明的形參類型

    1. 基本類型能從一個“較小”的類型自動提升至一個“較大”的類型
    2. char類型略有不同,如果無法找到恰好接受char參數的方法,就會把char直接提升至int型。
  • 實參類型 > 重載方法聲明的形參類型
    必須通過類型轉換來執行“窄化轉換”,否則編譯器會報錯。

5.2.3 以返回值區分重載方法

根據返回值來區分重載方法是行不通的。

5.3 默認構造器

不接受任何參數的構造器叫做默認構造器,構造器的名稱必須與類名完全相同。

如果類中沒有構造器,編譯器會自動創建一個默認構造器。

如果已經定義了一個構造器(無論是否有參數),編譯器則不會自動創建默認構造器。

5.4 this關鍵字

this關鍵字只能在方法內部使用,表示對“調用方法的那個對象”的引用。但要注意,如果在方法內部調用同一個類的別一個方法,就不必使用this。只有當明確指出對當前對象的引用時,才需要使用this關鍵字。

this關鍵字對於將當前對象傳遞給其他方法也很有用:

class Person {
    public void eat(Apple apple) {
        Apple peeled = apple.getPeeled();
        System.out.println("Yummy");
    }
}

class Peeler {
    static Apple peel(Apple apple) {
        // ... remove peel
        return apple; // Peeled
    }
}

class Apple {
    Apple getPeeled() {
        // 將自身對象傳遞給其他方法
        return Peeler.peel(this);
    }
}

public class PassingThis {
    public static void main(String[] args) {
        new Person().eat(new Apple());
    }
}
*/ Output:
Yummy
*/

5.4.1 在構造器中調用構造器

  • 儘管可以用this調用一個構造器,但卻不能調用兩個。
  • 必須將構造器調用置於最起始處。
  • 除構造器外,編譯器禁止在其他任何方法中調用構造器。
public class Flower {
    int petalCount = 0;
    String s = "initial value";

    Flower(int petals) {
        petalCount = petals;
        System.out.println("Constructor w/ int arg only, petalCount = " + petalCount);
    }

    Flower(String ss) {
        System.out.println("Constructor w/ String arg only, s = " + ss);
        s = ss;
    }

    Flower(String s, int petals) {
        this(petals);
        // 可以用this調用一個構造器,但卻不能調用兩個。
//        this(s); // Can't call two!
        this.s = s;
        System.out.println("String & int args");
    }

    Flower() {
        // 必須將構造器調用置於最起始處。
        this("hi", 47);
        System.out.println("default constructor (no args)");
    }

    void printPetalCount() {
        // 除構造器外,編譯器禁止在其他任何方法中調用構造器。
//        this(11); // Not inside non-constructor
        System.out.println("petalCount = " + petalCount + " s = " + s);
    }

    public static void main(String[] args) {
        Flower x = new Flower();
        x.printPetalCount();
    }
}
/* Output:
Constructor w/ int arg only, petalCount = 47
String & int args
default constructor (no args)
petalCount = 47 s = hi
*/

5.4.2 static的含義

  • static方法就是沒有this的方法,可以在沒有創建任何對象的前提下,僅僅通過類本身來調用static方法。
  • 在static方法的內部不能調用非靜態方法,反過來可以。

5.5 清理:終結處理和垃圾回收

5.6 成員初始化

  • 方法中的局部變量必須提供初始值
void f() {
    int i;
    i++; // Error -- i not initialized
}
  • 類的每個基本類型數據成員默認都會有初始值
  • 在類裏定義一個對象的引用(如下面的InitialValues)時,如果不將其初始化,此引用就會獲得一個特殊值null。
public class InitialValues {
    boolean t;
    char c;
    byte b;
    short s;
    int i;
    long l;
    float f;
    double d;
    InitialValues reference;

    void printInitialValues() {
        System.out.println("Data type       Initial value");
        System.out.println("boolean         " + t);
        System.out.println("char            " + c);
        System.out.println("byte            " + b);
        System.out.println("short            " + s);
        System.out.println("int            " + i);
        System.out.println("long            " + l);
        System.out.println("float            " + f);
        System.out.println("double            " + d);
        System.out.println("InitialValues            " + reference);
    }
    public static void main(String[] args) {
        InitialValues iv = new InitialValues();
        iv.printInitialValues();
    }
}

/* Output:
Data type       Initial value
boolean         false
char            [] 
byte            0
short           0
int             0
long            0
float           0.0
double          0.0
InitialValues   null
*/

5.6.1 指定初始化

1、在定義類成員變量的地方爲其賦值。

class Depth {}

public class InitialValues {
    boolean t = true;
    char c = 'x';
    byte b = 47;
    short s = 0xff;
    int i = 999;
    long l = 1;
    float f = 3.14f;
    double d = 3.14159;
    // 初始化非基本類型的對象
    Depth d = new Depth();
}

2、調用某個方法來提供初值

public class MethodInit {
    int i = f();
    int f() { return 11; }
}

3、調用某個方法來提供初值,方法可以帶參數,但參數必須已經被初始化

5.7 構造器初始化

5.7.1 初始化順序

在類的內部,變量定義的先後順序決定了初始化的順序。即使變量定義散佈於方法定義之間,它們仍舊會在任何方法(包括構造器)被調用之前得到初始化。

class Window {
    Window(int marker) {
        System.out.println("Window(" + marker + ")");
    }
}

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 OrderOfInitialization {
    public static void main(String[] args) {
        House h = new House();
        h.f();
    }
}
/* Output:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
*/

5.7.2 靜態數據的初始化

無論創建多少個對象,靜態數據都只佔用一份存儲區域。

static關鍵字不能應用於局部變量(即不用應用於方法或構造器中),因此它只能作用於域。

初始化的順序是先靜態對象,而後是“非靜態”對象。

類中的非靜態數據成員在每次創建新的對象時會進行初始化(如下面代碼中的7,9,11,bowl3初始化了3次),而靜態數據成員只有在第一次創建對象時纔會進行初始化。

class Bowl {
    Bowl(int marker) {
        System.out.println("Bowl(" + marker + ")");
    }

    void f1(int marker) {
        System.out.println("f1(" + marker + ")");
    }
}

class Table {
    // 2. 創建Bow1(1)對象
    static Bowl bowl1 = new Bowl(1);

    Table() {
        System.out.println("Table()");
        bowl2.f1(1);
    }

    void f2(int marker) {
        System.out.println("f2(" + marker + ")");
    }
    // 3. 創建Bowl(2)對象
    static Bowl bowl2 = new Bowl(2);
}

class Cupboard {
    // 7. 創建Bowl(3)對象
    // 9. 再創建Bowl(3)對象
    // 11. 第三次創建Bowl(3)對象
    Bowl bowl3 = new Bowl(3);
    // 5. 創建Bowl(4)對象
    static Bowl bowl4 = new Bowl(4);

    Cupboard() {
        System.out.println("Cupboard");
        bowl4.f1(2);
    }

    void f3(int marker) {
        System.out.println("f3(" + marker + ")");
    }
    // 6. 創建Bowl(5)對象
    static Bowl bowl5 = new Bowl(5);
}

public class StaticInitialization {
    public static void main(String[] args) {
        System.out.println("Creating new Cupboard() in main");
        // 8. 創建Cupboard對象
        new Cupboard();
        System.out.println("Creating new Cupboard() in main");
        // 10. 創建Cupboard對象
        new Cupboard();
        table.f2(1);
        cupboard.f3(1);
    }
    // 1. 創建Table對象
    static Table table = new Table();
    // 4. 創建Cupboard對象
    static Cupboard cupboard = new Cupboard();
}
/* Output:
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)
*/

5.7.3 顯示的靜態初始化

Java允許將多個靜態初始化動作組織成一個特殊的“靜態子句”,有時也叫做“靜態塊”。

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

5.7.4 非靜態實例初始化

與靜態初始化子句一樣,只是少了static關鍵字。

5.8 數組初始化

聲明數組的兩種方式:

int[] a;    // recommend
int a[];

聲明數組時不能指定其長度(數組中元素的個數)

數組初始化
通過上邊的定義,我們只是得到了一個數組的引用。這時已經爲引用分配了存儲空間,但是還沒有給數組對象本身分配任何空間。想要給數組對象分配存儲空間,必須使用初始化表達式。

初始化:
1.動態初始化:數組定義與爲數組分配空間和賦值的操作分開進行;

int[] a = new int[5];
for (int i = 0; i < a.length; i++) {
    a[i] = i;
}

2.靜態初始化:在定義數字的同時就爲數組元素分配空間並賦值;

int[] a1 ={1, 2, 3, 4, 5}; // 在數組創建的地方進行初始化
int[] a2 = new int[]{1, 2, 3, 4, 5};    //  該方法更靈活

3.默認初始化:數組是引用類型,它的元素相當於類的成員變量,因此數組分配空間後,每個元素也被按照成員變量的規則被隱士初始化。

int[] a = new int[5];

在Java中可以將一個數組賦值給另一個數組,所以可以這樣:

int[] a1 = {1, 2, 3, 4, 5}; // 在數組創建的地方進行初始化
int[] a2;
a2 = a1;    // 只是複製了一個引用,對a2的修改也會修改到a1
public class ArraysOfPrimitives {
    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]++;
        }
        for (int i = 0; i < a1.length; i++) {
            System.out.println("a1[" + i + "] = " + a1[i]);
        }
    }
}

所有的數組都有一個固定成員length,通過它可以知道數組元素的個數。
Output:

a1[0] = 2
a1[1] = 3
a1[2] = 4
a1[3] = 5
a1[4] = 6

5.8.1 可變參數列表(轉載)

下圖標出了參數列表的使用方式、格式和對傳入參數的要求。

  • 列表參數類型可以不同的情況
    可變參數類型爲Object,因爲所有的類都直接或間接的繼承自Object類,可以向上轉型爲Object,因此參數列表中的類型可以不一致。
    此處輸入圖片的描述
    輸出如下圖:
    此處輸入圖片的描述
  • 列表參數類型必須相同的情況
    當然可變參數參數列表也可以作爲函數的一個參數傳入,如下圖。這裏的參數列表中的參數類型爲string,則所有的參數必須是string類型,與上面的程序不同。
    此處輸入圖片的描述
  • 可變參數列表中的參數可以是任何類型,包括基本類型
    此處輸入圖片的描述
    此處輸入圖片的描述
  • 可變參數列表和自動包裝機制
    此處輸入圖片的描述
  • 可變參數列表與函數重載
    此處輸入圖片的描述
    在上面的所有帶參數的函數調用,編譯器都會使用自動包裝機制來匹配重載的方法,然後調用最匹配的方法。但是不使用使用參數來f()時,編譯器會二義性錯誤:

  Exception in thread “main” java.lang.Error: Unresolved compilation problem:
  The method f(Character[]) is ambiguous for the type OverloadingVarargs
  at thingjinjava.OverloadingVarargs.main(OverloadingVarargs.java:25)

那麼,如何解決這個問題呢?

可以嘗試着在方法中都增加一個非可變參數來解決該問題。
此處輸入圖片的描述

你應該總是隻在重載方法的一個版本上使用可變參數列表,或者壓根就不用它。

5.9 枚舉類型

Java SE5中添加了enum關鍵字

enum Spiciness {
    NOT, MILD, MEDIUM, HOT, FLAMING
}
public class SimpleEnumUse {
    public static void main(String[] args) {
        Spiciness howHot = Spiciness.MEDIUM;
        System.out.println(howHot);

        for (Spiciness s : Spiciness.values()) {
            System.out.println(s + ", ordinal " + s.ordinal());
        }
    }
}

這裏創建了一個名爲Spiciness的枚舉類型,它具有5個具名值。由於枚舉類型的實例是常量,因此按照命名慣例它們都用大寫字母表示。

enum有一個特別實用的特性,即它可以在switch語句中使用:

public class Burrito {
    Spiciness degree;

    public Burrito(Spiciness degree) {
        this.degree = degree;
    }

    public void describe() {
        System.out.println("This burrito is ");
        switch (degree) {
            case NOT:
                System.out.println("not spicy at all. ");
                break;
            case MILD:
            case MEDIUM:
                System.out.println("a little hot");
                break;
            case HOT:
            case FLAMING:
            default:
                System.out.println("maybe too hot");
        }
    }

    public static void main(String[] args) {
        Burrito plain = new Burrito(Spiciness.NOT),
                greenChile = new Burrito(Spiciness.MEDIUM),
                jalapeno = new Burrito(Spiciness.HOT);
        plain.describe();
        greenChile.describe();
        jalapeno.describe();
    }
}

Output:

This burrito is not spicy at all. 
This burrito is a little hot
This burrito is maybe too hot
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章