目錄表

國立屏東大學 資訊工程學系 物件導向程式設計

14. Inheritance and Overriding


Inheritance(繼承性)是指讓某一類別的物件繼承來自其它類別的屬性與行為。C++支援多重繼承,讓一個類別可以繼承多個類別;但Java語言僅支援單一繼承,這只得其成為較為簡單的語言,對初學者來說,是一件讓人輕鬆的事。不過Java也可以單一繼承、多重實作的方式,來達成多重繼承的效果。

14.1 IS-A relationship

「A is a kind of B.」

繼承性其實就是所謂的ISA關係 – IS-A relationship,是指兩個類別A與B間存在著:A類別就是B類別的一種特殊化。所謂的「特殊化(specialization)」是指A類別其實就是B類別,但A類別比B類別特殊些。我們將A類別稱為子類別,B類別則稱為父類別; 在Java語言的術語,則分別稱為sub class與super class。

回顧我們在前一章開始介紹的Person類別,每個屬於這個類別的物件都會具有firstname, lastname等屬性,以及showInfo()的行為。

public class Person 
{
   // field declarations
   private String firstname;
   private String lastname;
 
   Person()
   {
       firstname="unknown";
       lastname="unknown";
   }
 
   Person(String f, String l)
   {
       firstname=f;
       lastname=l;
   }
 
   // method declarations
   public void showInfo()
   {
      System.out.println("Name: " + firstname + " " + lastname );
   }
 
   public void setFirstname(String f) { firstname = f; }
   public void setLastname(String l) { lastname = l;}   
   public String getFirstname() {return firstname;}
   public String getLastname() {return lastname;}
 
}

STUScoreMan.Student

如果我們要設計一個學生成績管理系統(Student Score Management System, STUScoreMan),在我們所要建構的物件導向世界中,必然會存在有「學生」這種類別的物件。那要如何開始設計學生這個類別呢?先考慮學生只需具備姓名與學號兩個屬性,那麼我們可以撰寫以下的程式碼:

class Student
{
   // field declarations
   String firstName;
   String lastName;
   String ID;
 
   // constructors
   ...
   // method delcarations
   ...
}

就如同前一章所介紹的,我們還應該為這個類別加上建構子、顯示學生資料的method、設定存取權限、設計setters與getters…。我們發現這個新設計的Student類別,與Person類別很相似….

「Student也是一種Person,只是比較特別!比起Person,Student還多了ID(目前為止)。」

以這樣的角度看問題時,我們可以說「Student is a kind of Person!」。我們可以用以下的程式碼,告訴電腦這件事:

class Student extends Person { }

「extends」是Java的keywords之一,用以表示Student類別是「延伸自」Person類別,換句話說,Student類別要繼承Person類別。

在這個ISA的繼承關係中,我們將Person類別稱為「父類別(super class)」,Student類別則稱為「子類別(sub class)」。

一旦你完成這樣的設計,儘管現在在Student類別中,一行程式碼都還沒寫,但Person類別該有的,Student類別也都會有,包含Person類別的屬性與行為。因此,我們可以在物件導向的世界中,把Student類別的物件視為是Person類別的物件,並且使用它所公開(public)的屬性與行為。請參考下面的程式碼:

public class test {
 
    public static void main(String[] args) {
        Student amy;
        amy = new Student();
        amy.setFirstname("Amy");
        amy.setLastname("Wang");
        amy.showInfo();
    }
}

沒有意外地,其執行結果如下:

Name: Amy Wang

但這不是我們要的結果,雖然Student已經是a kind of Person,但Student不夠特殊,它跟Person根本是一樣的。讓我們將新的屬性與行為加到Student類別中,請參考下面的程式碼:

public class Student extends Person
{
    // fields
    private String ID;
 
    // contructors
    Student()
    {
        setFirstname("unknown");
        setLastname ("unknown");
        ID = "unknown";
    }
 
    Student(String f, String l, String i)
    {
        setFirstname(f);
        setLastname(l);
        ID = i;
    }
    // methods
    public void setID(String id) { ID=id; }
    public String getID() { return ID; }
}

在Person.java中,firstname與lastname是定義為private,所以連子類別都不能使用,必須透過其setters與getters才能存取。

讓我們為這個小節做個總結:

「Student IS A (kind of) Person」

在Java語言裡,使用extends這個keyword來完成的ISA繼承關係,子類別(subclass)可以繼承父類別(super class)所有的屬性與行為,除了那些被定義為私有的(private)。

14.1.1 instanceof

如果在程式執行時,我們想知道某個物件是屬於何種類別,那可以使用instanceof運算子。

System.out.println(amy instanceof Person);
System.out.println(amy instanceof Student);

其輸出結果為:

true
true

instanceof運算傳回的是boolean值,表示amy這個物件是Person類別的實體,同時它也是Student類別的實體。

14.2 this and super

14.2.1 this

在類別定義理,「this」表示自己這個類別。我們可以將其建構子改寫如下:

    Student()
    {
        // 等於呼叫Student("unknown", "unknown", "unknown");
        this("unknown", "unknown", "unknown"); 
    }
 
    Student(String f, String l, String i)
    {
        setFirstname(f);
        setLastname(l);
        ID = i;
    }

14.2.2 super

在類別定義裡,「super」表示自己所繼承的父類別(super class)。我們可以將其建構子改寫如下:

    Student()
    {
        // 等於呼叫Student("unknown", "unknown", "unknown");
        this("unknown", "unknown", "unknown"); 
    }
 
    Student(String f, String l, String i)
    {
        super(f,l);  // 等於去呼叫父類別的Person(f,l)
        ID = i;
    }

14.3 Overriding (覆載)

STUScoreMan.Teacher

再舉一個例子,同樣針對這個STUScoreMan,我們還需要設計一個名為Teacher的類別。每個老師當然會有姓名,以及「一項」專長(當然只是假設啦,我們有些老師連廚師證照都有呢!)。首先,我們針對專長的部份,設計一個列舉型別,如下:

public enum Expertise 
{
   Java, OS, Database, Algorithms, Networking
}

接著透過同樣透過繼承Person來快速地完成Teacher類別的設計:

public class Teacher extends Person
{
   private Expertise expertise;
 
   Teacher()
   {
      this("unknown","unknown", null);
   }
 
   Teacher(String f, String l, Expertise exp)
   {
      super(f,l);
      expertise = exp;
   }
 
   public void setExpertise(Expertise exp) { expertise = exp; }
   public Expertise getExpertise() { return expertise; }
}

可是當我們使用以下這行來宣告並產生一個Teacher類別的物件實體時:

Teacher junwu = new Teacher("Jun", "Wu", Expertise.Java);

若呼叫「junwu.showInfo()」,我們只會得到這位老師的姓名,並沒有其它資訊。所以我們將在Teacher.java中加入以下的method:

   public void showInfo()
   {
       super.showInfo();
       System.out.print("Expertise: ");
       switch(expertise)
       {
          case Java:
               System.out.println("Java Programming");
               break;
          case OS:
          case Database:
          case Algorithm:
          case Networking:
               System.out.println("something interesting");
       }
   }

在ISA的繼承關係中,若sub class定義與super class相同的method,就稱為「Overriding」,意即提供新的版本供sub class類別使用。若是要呼叫原本super class類別中的method時,可以使用super.XXX()方式達成,例如「super.showInfo()」,就是呼叫其所繼承的super類別的showInfo() method,其結果可以輸出其姓名;然後我們再進一步將其專長輸出。

Overriding常用於提供不同於super class的method版本,畢竟ISA是一種特殊化的關係,sub class與super class十分相同,也繼承了來自super class的一切定義與宣告,可是可以針對自己的特殊性,定義自己的method。由於是做同一件事情,所以我們使用同樣的method名稱,以符合「真實世界」的通則(當然,這也是一種簡化,否則我們可能要提供showTeacherInfo()這種命名,如此會使得不同class對同一件事情的操作方法不同)。修改後完整的程式碼如下:

public class Teacher extends Person
{
   private Expertise expertise;
 
   Teacher()
   {
      this("unknown","unknown", null);
   }
 
   Teacher(String f, String l, Expertise exp)
   {
      super(f,l);
      expertise = exp;
   }
 
   public void setExpertise(Expertise exp) { expertise = exp; }
   public Expertise getExpertise() { return expertise; }
 
   public void showInfo()
   {
       super.showInfo();
       System.out.print("Expertise: ");
       switch(expertise)
       {
          case Java:
               System.out.println("Java Programming");
               break;
          case OS:
          case Database:
          case Algorithm:
          case Networking:
               System.out.println("something interesting");
       }
   }
}

要覆載一個Mthod必須遵守以下的規則:

14.4 HAS-A relationship

「A has a B」

類別間的HAS-A關係是指某類別包含有其它類別。例如「A has a B」,如此一來,在A類別的程式碼中,還可以透過其擁有的B類別的物件,去使用B類別所定義的屬性或行為。

回顧我們所要設計的一個學生成績管理系統(Student Score Management System, STUScoreMan),假設一個學生,只能修一門課,那麼在我們所要建構的物件導向世界中,必然會存在有「學生」這種類別的物件,以及「課程」類別的物件。

我們先設計一個課程的類別如下(其中包含有授課教師的資料):

public class Course
{
   private String courseName;
   private String classroom;
   private Teacher teacher;   // Here is a HAS-A relationship
 
   Course()
   {
      this("unknown", null, "unknown");
   }
 
   Course(String cn, Teacher t, String cr)
   {
      courseName = cn;
      teacher = t;
      classroom = cr;
   }
 
    public void showInfo()
    {
        System.out.println("Course Name:" + courseName);
        System.out.println("Teacher: ");
        teacher.showInfo();
        System.out.println("Classroom: " + classroom);
    }
 
    public String getCourseName()
    {
        return courseName;
    }
 
}

我們也將Student.java進行部份的修改,加上「一個學生只能修一門課」的假設,以及將showInfo()加以overriding,其程式碼如下:

public class Student extends Person
{
    // fields
    private String ID;
    private Course course; // Here is a HAS-A relationship
 
    // contructors
    Student()
    {
        this("unknown","unknown","unknown"); //改成用this
        ID = "unknown";
    }
 
    Student(String f, String l, String i)
    {
        super(f,l);
        ID = i;
    }
    // methods
    public void setID(String id) { ID=id; }
    public String getID() { return ID; }
 
    public void takeCourse(Course c)
    {
        course = c;
    }
 
    public void showInfo()
    {
        super.showInfo();
        if(course!=null)
            System.out.println("Course: " + course.getCourseName());
    }
 
}

這兩個類別中,都含有Has-A的關係,例如Course中的「private Teacher teacher;」與Student中的「private Course course;」。

最後,讓我們設計一個Main類別,以便測試程式是否正確執行。

public class StudentScoreMan {
 
    public static void main(String[] args) {
 
        Student amy;
        amy = new Student();
 
        amy.setFirstname("Amy");
        amy.setLastname("Wang");
        amy.showInfo();
 
        System.out.println(amy instanceof Person);
        System.out.println(amy instanceof Student);
 
        Teacher junwu = new Teacher("Jun", "Wu", Expertise.Java);
        Course oop = new Course("Object-Oriented Programming", junwu, "CC0802");
        junwu.showInfo();
        oop.showInfo();
 
        amy.takeCourse(oop);
        amy.showInfo();
    }
}

Name: Amy Wang
Name: Jun Wu
Expertise: Java Programming
Course Name:Object-Oriented Programming
Teacher: 
Name: Jun Wu
Expertise: Java Programming
Classroom: CC0802
Name: Amy Wang
Course: Object-Oriented Programming