基本觀念

Encapsulation,Message Passing以及Inheritance是構成Object-Oriented的三大要素,如果某程式語言只具備前面兩項特性,一般成為Object-Based。所謂Inheritance(繼承),是指Sub Class(子類別)繼承Super Class(父類別)後,就會自動取得父類別特性。如果子類別繼承了一個以上的父類別,則稱為Multiple Inheritance(多重繼承)。Java為了避開多重繼承的複雜性, class只允許單一繼承。

Java使用關鍵字extends來表達繼承觀念:

public class Animal {
    public String moveMethod() {
        return "Unspecified";
    }
}
public class Bird extends Animal {
    public String moveMethod() {
        return "Fly";
    }
}
public class Dog extends Animal {
    public String moveMethod() {
        return "run";
    }
}
public class Fish extends Animal {
    public String moveMethod() {
        return "swim";
    }
}

若class宣告時沒有指定extends,則Java會自動extends java.lang.Object。

public class A {
}

和下面的寫法相同

public class A extends java.lang.Object {
}

UpCasting(向上轉型)和DownCasting(向下轉型)

所謂casting是指型態轉換, UpCasting是將子類別型態的reference轉型為父類別型態, DownCasting則是將父類別型態的reference轉型成子類別型態。由於子類別可以視為和父類別相容,如Fish, Dog, Bird都是一種Animal, 因此UpCasting一定沒有問題:

Animal a;
Bird b;
a = b; // upcasting, Bird is a kind of Animal

父類別的reference可以指到子類別的Object,這種觀念稱為Polymorphism(多型)

但在downcasting的情況下, 父類別的reference和子類別並不相容, 如Animal不見得是一個Bird, 因此必須使用(SubClass)的casting語法來做強迫轉換。

Animal a = new Bird(); // upcasting
Bird b;
b = (Bird)a; // downcasting, compile correct
if (a instanceof Bird) { // true
}

downcasting除了必須由設計者下達外, JVM在runtime也會檢查實際的物件能否和reference的型態相容

Animal a = new Dog(); // upcasting
Bird b;
b = (Bird) a; // downcasting, compile correct, but runtime error

比較完整的範例如下

public class InheritanceExample {
    public static void main(String[] argv) {
        Animal a1, a2, a3, a4;
        Bird b;
        Dog d;
        Fish f;
        a2 = a1 = new Animal();
        b = new Bird();
        d = new Dog();
        f = new Fish();
        System.out.println(a1.moveMethod());
        System.out.println(b.moveMethod());
        System.out.println(d.moveMethod());
        System.out.println(f.moveMethod());
        a1 = b; // Correct, we call this upcasting
        b = a1; // Compile Error, type not compatible
        b = (Bird)a1; // downcasting, Compile Correct
        a2 = b; // Correct,we call this upcasting
        d = a2; // Compile Error, type not compatible
        d = (Dog)a2; // Compile Correct, but runtime error
    }
}

Override(覆寫)

子類別重新定義它所能看到的父類別中的method(如public, protected, 如果子類別和父類別在同一個package裡, 則沒有修飾字的method也可以), 稱為override。

public class Animal {
    public String moveMethod() {
        return "Unspecified";
    }
}
public class Bird extends Animal {
    // override Animal's moveMethod
    public String moveMethod() {
        return "Fly";
    }
}

要特別強調的是

public class C2 {
    public void a() {}
}
public class C1 extends C2 {
    protected void a() { // Compile Error,不得縮小存取範圍
    }
}

Virtual Function(虛擬函數)

訊息傳遞的章節裡,我們有提到過Object接收到訊息後,是在Runtime才決定實際所要呼叫的Method。由於父類別的reference可以指到子類別物件(Polymorphism),而子類別和父類別可能都定義了相同的Method(Override),當使用父類別reference傳遞訊息給子類別物件時,應該要呼叫父類別的方法還是子類別的方法? 如果

有些程式語言,如C++,以上兩種機制都提供,可由設計者自行決定。但是Java語言為了遵循物件導向的精神,並避免設計者因語言設計複雜而犯錯,因此只提供了Virtual Function。

public class InheritanceExample {
    public static void main(String[] argv) {
        Animal a1;
        a1 = new Animal();
        System.out.println(a1.moveMethod()); // print out "Unspecified"
        a1 = new Bird(); // polymorphism
        System.out.println(a1.moveMethod()); // print out "Fly"
    }
}

請注意上一小節所提到Override的注意事項

class Animal {
    public static String moveMethod() {
        return "Unspecified";
    }
    public static void main(String[] argv) {
        Animal a1;
        a1 = new Bird();
        System.out.println(a1.moveMethod()); // print out "Unspecified"
    }
}
class Bird extends Animal {
    // we can't override static method
    public static String moveMethod() {
        return "Fly";
    }
}

上面的moveMethod()由於宣告為static,因此是依照reference的type來決定執行的method。

class Animal {
    private String moveMethod() {
        return "Unspecified";
    }
    public static void main(String[] argv) {
        Animal a1;
        a1 = new Bird();
        System.out.println(a1.moveMethod()); // print out "Unspecified"
    }
}
class Bird extends Animal {
    // this is not override because Bird can't see Animal's moveMethod
    public String moveMethod() {
        return "Fly";
    }
}

由於上面Animal內的moveMethod宣告為private,因此執行時印出"Unspecified"。

採用Virtual Function的優點

缺點

本章觀念整理範例

public class Shape2D { // define super class
    public double area() { // all Shape2D have their own area
        return 0;
    }
}
public class Rectangle extends Shape2D {
    private double length, width;
    public Rectangle(double l, double w) { // define constructor
        length = l;
        width = w;
    }
    public double area() { // Override
        return length * width;
    }
}
public class Circle extends Shape2D {
    private double radius;
    public Circle(double r) {
        radius = r;
    }
    public double area() { // Override
        return 3.141592654 * radius * radius;
    }
}
public class Parallelogram extends Shape2D {
    private double top, bottom, height;
    public Parallelogram(double t, double b, double h) {
        top = t;
        bottom = b;
        height = h;
    }
    public double area() { // Override
        return (top + bottom) * height / 2.0;
    }
}
publicclass Main {
    public static double sum(Shape2D[] shapes) {
        double total = 0;
        for (int i = 0; i < shapes.length; i++) {
            total += shapes[i].area(); // use Virtual Function to calculate area of Shape2D
                                       // Without Virtual Function, value of Shape2D.area() will be 0
        }
        return total;
    }
    public static void main(String[] argv) {
        Shape2D[] data; // array of reference to Shape2D
        data = new Shape2D[5]; // create array object
        data[0] = new Rectangle(2.4, 3.8); // Polymorphism
        data[1] = new Circle(3.9);
        data[2] = new Parallelogram(3.5, 6.7, 10.2);
        data[3] = new Rectangle(5.3, 7.2);
        data[4] = new Circle(4.6);
        System.out.println("Sum of all Shape2D is "+sum(data));
     }
}

如果程式語言不支援virtual function的話, 則上面的範例就得寫成下面的形式才行

public class Main { // example for non-virtual function implementation
    public double sum(Shape2D[] shapes) {
        double total = 0;
        for (int i = 0;  i < shapes.length; i++) {
            if (shapes[i] instanceof Rectangle) {
                total += ((Rectangle)shapes[i]).area();
            } else if (shapes[i] instanceof Circle) {
                total += ((Circle)shapes[i]).area();
            } else if (shapes[i] instanceof Parallelogram) {
                total += ((Parallelogram)shapes[i]).area();
            } // modify source code here for new sub classes
        }
        return total;
    }
    public static void main(String[] argv) {
        Shape2D[] data; // array of reference to Shape2D
        data = new Shape2D[5]; // create array object
        data[0] = new Rectangle(2.4, 3.8); // Polymorphism
        data[1] = new Circle(3.9);
        data[2] = new Parallelogram(3.5, 6.7, 10.2);
        data[3] = new Rectangle(5.3, 7.2);
        data[4] = new Circle(4.6);
        System.out.println("Sum of all Shape2D is "+sum(data));
     }
}

final修飾字

final除可用來修飾變數外,也可放在class和object method前面:

public final class FinalClass {
    public final void finalMethod() {
    }
}

放在class前面表示class不可被繼承, 放在object method表示不可被Override。

繼承關係下的Constructor執行順序

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

如果要指定父類別其他的constructor,則必須在子類別的constructor的第一行使用關鍵字super來處理。

class Animal {
    int aMask = 0x00FF;
    public Animal() {
    }
    public Animal(int mask) {
        aMask = mask;
    }
}
public class Bird extends Animal {
    int bMask = 0xFF00;
    int fullMask;
    public Bird() {
        // Compiler add super() here
        fullMask = bMask | aMask;
    }
    public Bird(int mask) {
        /* 若有super,則必須放在第一行,連變數宣告也不能擺在super前面 */
        super(mask);
        fullMask = bMask | aMask;
    }
    public static void main(String[] argv) {
        Bird b = new Bird();
        System.out.println(b.fullMask);
        b = new Bird(0x0011);
        System.out.println(b.fullMask);
    }
}

當執行new Bird()時,此物件內各個變數的變化如下

步驟aMaskbMaskfullMask
default000
call Bird()000
call Animal()000
Animal initialize0x00FF00
execute Animal()0x00FF00
Bird initialize0x00FF0xFF000
execute Bird()0x00FF0xFF000xFFFF

當執行new Bird(0x0011)時,此物件內各個變數的變化如下

步驟aMaskbMaskfullMask
default000
call Bird(0x0011)000
call Animal(0x0011)000
Animal initialize0x00FF00
execute Animal(0x0011)0x001100
Bird initialize0x00110xFF000
execute Bird(0x0011)0x00110xFF000xFF11