類別與物件的基本概念

所謂物件,說得白話一點,可稱之為"東西"。這是個很抽象的名詞,我們若以它具體的特性來描述,會比較清楚:

屬於同一個Class的Object,會具有該Class所定義的以上三種特質。

除此之外,Class之間可以定義繼承(Inheritance)關係,子類別(Sub Class)繼承父類別(Super Class)的所有特性,子類別還可以定義其專屬的特性。

以Object-Oriented(物件導向) Language寫作程式時,寫作的主體是Class。Class定義了所有屬於該Class的Object的特性,這些特性可分類如下:

如何以Java撰寫類別

Java規定公共類別(public class)必須寫在該公共類別名稱的.java檔案內, 例如public class Example就必須寫在Example.java這個檔案內。Example.java裡面也可以定義其他的類別,但是只有class Example能夠宣告為public,其他Example.java裡的class都不能宣告為public。當Java Virtual Machine啟動時,它會去找命令列上所指定的class裡的public static void main(String[] argv)方法,當做是程式的進入點。這有點像是C語言的main, 不同處在於每個java class都可以定義自己的public static void main(String[] argv)。

java Example

啟動上述的JVM時, JVM會去執行class Example裡的public static void main(String[] argv)。以下範例Example.java說明如何定義Java的class。

class Vehicle {
    private int speed; // Object Variable
    private String direction; // Object Variable, direction is a reference to String Object
    private static int numVehicle = 0; // Class Variable
    public Vehicle() { // Constructor, called when new a Object
        this(0,"north"); // call another constructor to do initialization
    }
    public Vehicle(int s, String dir) { // Another Constructor. Use overloading to define two constructors
        float speed; // define a local variable
        speed = s; // the speed here refers to the above local variable
        this.speed = s; // If we want to set object variable, use this.speed to refer object variable speed
        direction = dir; // dir is a reference to object, not the object itself
        numVehicle++;   // increase the Vehicle number
    }
    protected void finalize() { // Destructor, called when the object is garbage collected by JVM
        System.out.println("finalize has been called");
        numVehicle--;
    }
    void setSpeed(int newSpeed) { // Object Method
        this.speed = newSpeed;
    }
    void setDir(String dir) { // Object Method
        this.direction = dir;
    }
    int getSpeed() { // Object Method
        return speed;
    }
    String getDir() { // Object Method
        return direction;
    }
    public static int totalVehicle() { // Class Method
        return numVehicle;
    }
}
public class Example {
    public static void main(String[] argv) {
        Vehicle v1 = new Vehicle(50, "west"); // new 敘述用來產生物件. 物件產生時需要呼叫Constructor來初始化物件
        Vehicle v2;
        v1.setSpeed(30);
        v1.setDir("north");
        System.out.println("V1: speed is "+v1.getSpeed()+", direction is "+v1.getDir()+".\n");
        v2 = new Vehicle(40, "south");
        System.out.println("There are "+Vehicle.totalVehicle()+" Vehicles in the world.\n");
        v1 = v2; // let reference v1 point to where v2 is pointing
        System.out.println("V1: speed is "+v1.getSpeed()+", direction is "+v1.getDir()+".\n");
        System.gc(); // force system to do garbage collection, the object previously pointed by v1 shall be destroyed
        System.out.println("There are "+Vehicle.totalVehicle()+" Vehicles in the world.\n");
    }
}

上述例子裡所用到的關鍵字或類別名稱說明如下:

Object Method的名稱如果和Class的名稱相同, 則表示該Method為Constructor。Constructor不能宣告傳回值。

要附帶說明的是, Java以new指令來產生物件, 但不像C++有提供相對應的delete指令來消滅物件。Java採用Garbage Collection的觀念,當系統於閒置期間自動呼叫或由使用者強制呼叫System.gc()時,沒有被任何reference指到的Object就會被回收。

Class裡面一定要定義一個以上的Constructor, 但為了方便起見,如果Compiler發現某Class沒有定義Constructor,則Compiler會幫我們產生一個不做任何事的Constructor:

public class A {
}

就相當於

public class A {
    public A() {}
}

Overloading

同一個class裡的Method名稱可以重複使用,只要可以由Method的參數個數和型態來區分就可以了。這種觀念稱為overloading。

不只一般的method可以overloading, constructor也可以overloading。

public class Overloading {
    int data;
    public Overloading() {
        this(0); // call constructor Overloading(int)
    }
    public Overloading(int data) {
        this.data = data;
    }
    public void print() {
        this.print(0); // call method print(int)
    }
    public void print(int msg) {
    }
    public void print(float msg) {
    }
    public void print(int msg, String others) {
    }
}

上面的例子裡說明constructor也可以overloading。要特別注意的是,傳回值並不能用來分辨要呼叫哪個method,因此若再加上public int print()的宣告,就會造成編譯錯誤了。

初始化的執行順序

Class variable是在該類別載入JVM時進行初始化的, 因此寫作上經常在class variable的宣告後面加上初始化的動作。對Object Variable來說, 是在產生Object時進行初始化的, 但初始化的步驟可以寫在變數宣告後, 也可以寫在constructor內, 因此必須對其執行順序有所了解。步驟如下:

  1. 先將所有變數設為內定值。對數值型態來說, 其值為0;對reference來說, 其值為null;對boolean來說, 其值為false。
  2. 呼叫父類別的constructor。
  3. 執行變數宣告的初始化動作。
  4. 執行自己的constructor。

因此在如下的範例內

public class InitSequence {
    int data = 2;
    public InitSequence(int data) {
        this.data = data;
    }
    public static void main(String[] argv) {
        InitSequence s = new InitSequence(3);
        System.out.println(s.data);
    }
}
data的變化如下
  1. 設為內定值0
  2. 呼叫父類別的Constructor。因為類別InitSequence沒有宣告繼承任何類別, Java規定此情況會自動繼承java.lang.Object這個類別。Object的Constructor不做任何事。
  3. 執行變數宣告的初始動作,成為2
  4. 執行自己的constructor,成為3

因此最後執行的結果會在螢幕上印出數字3。

Java語言還可以定義static block:

public class StaticBlock {
    static { // this is a static block
        data = (int)(Math.random()*100);
    }
    static int data;
    public static void main(String[] argv) {
        System.out.println(data);
    }
}

static block內的程式碼, 是在該class載入JVM之後, 進行class variable初始化之前的時間內執行。一般比較會使用static block的場合, 是該class用到一些非Java的程式庫, 需要透過System.loadLibrary(String libName)方法把外界的程式碼載入時。這樣寫的好處是只有當該class第一次被使用到時, 才會下載相關軟體, 以節省記憶體空間, 避免重複下載, 並可以把實作的細節和外界隔離開來。對沒有這種機制的C語言來說, 很可能就必須在主程式內寫上一堆很難懂的啟動程式碼。

class ClassNeedToLoadLibrary {
    static {
        System.loadLibrary("mylib");
    }
}
public class Main {
    public static void main(String[] argv) {
    }
}

final關鍵字

final關鍵字用在變數宣告時,表示該變數的值只能在宣告時給定,然後就不能再更改了。

public class Main {
    public static final double PI = 3.14159;
    public final int x = 10;
    public static void main(String[] argv) {
        final int local = 10;
        Main m = new Main();
        PI = 100; // Compile Error, final variable can only be set at initialization
        m.x = 10; // Compile Error
        local = 100; // Compile Error
    }
}