【java基礎(二十三)】類、超類、子類(一)

接下來,我們學習面向對象程序設計的另外一個基本概念,也是Java程序設計中的一項核心技術:繼承(inheritance)。利用繼承,可以基於已存在的類構造一個新類,繼承已存在的類就是複用(繼承)這些類的方法和域。在此基礎上,可以添加一些新的方法和域,以滿足新的需求。
回憶一下我們一直使用的Employee類。一個公司中經理的待遇與普通員工的待遇存在着一些差異,不過,他們之間也存在着很多相同的地方,總的來說,都屬於員工範疇。例如:他們都領取薪水,只是普通員工在完成本職任務之後僅僅領取薪水,而經歷在完成了預期的業績之後還能得到獎金。這種情況就需要繼承。因爲需要爲經歷定義一個新類Manager,以便增加一些新功能。但可以重用Employee類中已經編寫的部分代碼,並將其中的所有域保留下來。從理論上將,在Manager與Employee類之間存在着明顯的“is - a”關係,每個經理都是一名僱員:“is - a”關係是繼承的一個明顯特徵。

定義子類

下面是由繼承Employee類來定義Manager類的格式,關鍵字extends表示繼承。

public class Manager extends Employee {
	// 添加方法和域
}

關鍵字extends表明正在構造的新類派生於一個已存在的類。已存在的類成爲超類(superclass)、基類(base class)或父類(parent class);新類成爲子類(subclass)、派生類(derived class)或孩子類(child class)。超類和子類是Java程序猿最常用的兩個術語。
儘管Employee類是一個超類,但並不是因爲它由於子類或者比子類有更多的功能。實際上恰恰相反,子類比超類擁有的功能更加豐富。
在Manager類中,增加了一個用於存儲獎金信息的域,以及一個用於設置這個域的新方法:

public class Manager extends Employee {
	private double bonus;
	...
	public void setBonus(double bonus) {
		this.bonus = bonus;
	}
}

這裏定義的方法和域並沒有什麼特別之處。如果有一個Manager對象,就可以使用setBonus方法。

Manager boss = ...;
boss.setBonus(1000);

當然,由於setBonus方法不是在Employee類中定義的,所以屬於Employee類的對象不能使用它。
然而,儘管在Manager類中沒有顯式地定義getName和getHireDay等方法,但屬於Manager類的對象卻可以使用它們,這是因爲Manager類自動地繼承了超類Employee中的這些方法。
同樣,從超類中還繼承了name、salary和hireDay這三個域。這樣一來,每個Manager類對象包含了4個域:name、salary、hireDay和bonus。
在通過擴招超類定義子類的時候,僅需要指出子類與超類的不同之處。因此在設計類的時候,應該將通用的方法放在超類中,而將具有特殊用途的方法放在子類中,這種將通用的功能放到超類的做法,在面向對象程序設計中十分普遍。

覆蓋方法

當然,超類中的有些方法可能對子類Manager並不一定適用。如,Manager類中的getSalary方法應該返回薪水和獎金的總和。爲此,需要提供一個新的方法來覆蓋(override)超類中的這個方法:

public class Manager extends Employee {
	...
	public double getSalary() {
		...
	}
	...
}

應該如何實現這個方法呢?乍看起來似乎很簡單,只要返回salary和bonus域的總和就可以了:

public double getSalary() {
	return salary + bonus;
}

然而,這個方法並不能運行。這是因爲Manager類的getSalary方法不能直接地訪問超類的私有域。也就是說,儘管每個Manager對象都擁有一個名爲salary的域,但在Manager類的getSalary方法中並不能夠直接地訪問salary域。只有Employee類的方法才能夠訪問私有部分。如果Manager類的方法一定要訪問私有域,就必須藉助於共有的接口,Employee類中的共有方法getSalary正是這樣一個接口。

public double getSalary() {
	double baseSalary = getSalary();
	return baseSalary + bonus;
}

上面這段代碼仍然不能運行。問題出現在調用getSalary的語句上,這是因爲Manager類也有一個getSalary方法(就是正在實現的方法),所以這條語句將會導致無限次的調用自己,直到整個程序崩潰。
這裏需要注意,我們希望調用超類Employee中的getSalary方法,而不是當前類的這個方法。爲此,可以使用特定的關鍵字super解決這個問題:

super.getSalary();

下面我們看一下完整的方法:

public double getSalary() {
	double baseSalary = super.getSalary();
	return baseSalary + bonus;
}

正像前面所看到的那樣,在子類中可以增加域、增加方法或覆蓋超類的方法,然而絕對不能刪除繼承的任何域和方法。

子類構造器

我們來看一下Manager的構造器方法:

public Manager(String name, double salary, int year, int month, int day) {
	super(name, salary, year, month, day);
	bonus = 0;
}

這裏的關鍵字super的含義是“調用超類Employee”中對應的構造器的簡寫形式。
由於Manager類的構造器不能訪問Employee類的私有域,所以必須利用Employee類的構造器對這部分私有域進行初始化,我們可以通過super實現對超類構造器的調用。使用super調用構造器的語句必須是子類構造器的第一條語句。
如果子類的構造器沒有顯式地調用超類的構造器,則將自動地調用超類默認(沒有參數)的構造器。如果超類沒有不帶參數的構造器,並且在子類的構造器中又沒有顯式地調用超類的其他構造器,則Java編譯器將報錯。
接下來我們創建一個新經理(Manager),並設置他的獎金:

Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);

下面定義一個包含3個僱員的數組,並將經理和員工都放到數組中。

Employee[] staff = new Employee[3];
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);

輸出每個人的薪水:

for (Employee e : staff)
	System.out.println(e.getName() + " " + e.getSalary());

這裏staff[1]和staff[2]僅輸出了基本薪水,這是因爲它們對應的是Employee對象,而staff[0]對應的是Manager對象,它的getSalary方法將獎金和基本薪水加在了一起。
需要提到的是:

e.getSalary();

這個能夠確定應該執行哪個getSalary方法。請注意,儘管這裏e聲明爲Employee類型,但實際上e即可以引用Employee類型的對象,也可以引用Manager類型的對象。
當e引用Employee對象時,e.getSalary()調用的是Employee類中的getSalary方法;當e引用Manager對象時,e.getSalary()調用的是Manager類中的getSalary方法。虛擬機知道e實際引用的對象類型,因此能夠正確地調用相應的方法。
一個對象類型(例如,變量e)可以只是多種類型的現象稱爲多態(polymorphism)。在運行時能夠自動地選擇調用哪個方法的現象稱爲動態綁定(dynamic binding)。

實例

Employee.java

package cn.freedompc.inheritance;

import java.time.LocalDate;

public class Employee {

	private String name;
	private double salary;
	private LocalDate hireDay;
	
	public Employee(String name, double salary, int year, int month, int day) {
		this.name = name;
		this.salary = salary;
		hireDay = LocalDate.of(year, month, day);
	}
	
	public String getName() {
		return name;
	}
	
	public double getSalary() {
		return salary;
	}
	
	public LocalDate getHireDay() {
		return hireDay;
	}
	
	public void raiseSalary(double byPercent) {
		double raise = salary * byPercent / 100;
		salary += raise;
	}
	
}

Manager.java

package cn.freedompc.inheritance;

public class Manager extends Employee {
	
	private double bonus;

	public Manager(String name, double salary, int year, int month, int day) {
		super(name, salary, year, month, day);
		bonus = 0;
	}
	
	public double getSalary() {
		double baseSalary = super.getSalary();
		return baseSalary + bonus;
	}
	
	public void setBonus(double b) {
		bonus = b;
	}

}

ManagerTest.java

package cn.freedompc.inheritance;

public class ManagerTest {

	public static void main(String[] args) {
		Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
		boss.setBonus(5000);
		
		Employee[] staff = new Employee[3];
		
		staff[0] = boss;
		staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
		staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
		
		for (Employee e : staff)
			System.out.println("name = " + e.getName() + ", salary = " + e.getSalary());
	}
	
}

結果

在這裏插入圖片描述

捐贈

若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章