P09_繼承-super-this-抽象類
第一章 繼承
1.1 概述
由來
多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那一個類即可。
其中,多個類可以稱為子類,單獨那一個類稱為父類、超類(superclass)或者基類。
繼承描述的是事物之間的所屬關係,這種關係是: is-a 的關係。父類更通用,子類更具體。我們通過繼承,可以使多種事物之間形成一種關係體系。
定義
- 繼承:就是子類繼承父類的屬性和行為,使得子類物件具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。
好處
- 提高程式碼的複用性。
- 類與類之間產生了關係,是多型的前提。
1.2 繼承的格式
通過 extends 關鍵字,可以宣告一個子類繼承另外一個父類,定義格式如下:
/* * 定義員工類Employee,做為父類 */ class Employee { String name; // 定義name屬性 // 定義員工的工作方法 public void work() { System.out.println("盡心盡力地工作"); } } /* * 定義講師類Teacher 繼承 員工類Employee */ class Teacher extends Employee { // 定義一個列印name的方法 public void printName() { System.out.println("name=" + name); } } /* * 定義測試類 */ public class ExtendDemo01 { public static void main(String[] args) { // 建立一個講師類物件 Teacher t = new Teacher(); // 為該員工類的name屬性進行賦值 t.name = "小明"; // 呼叫該員工的printName()方法 t.printName(); // name = 小明 // 呼叫Teacher類繼承來的work()方法 t.work(); // 盡心盡力地工作 } }
1.3 繼承後的特點——成員變數
當類之間產生了關係後,其中各類中的成員變數,又產生了哪些影響呢?
成員變數不重名
如果子類父類中出現不重名的成員變數,這時的訪問是沒有影響的。程式碼如下:
class Fu { // Fu中的成員變數。 int num = 5; } class Zi extends Fu { // Zi中的成員變數 int num2 = 6; // Zi中的成員方法 public void show() { // 訪問父類中的num, System.out.println("Fu num="+num); // 繼承而來,所以直接訪問。 // 訪問子類中的num2 System.out.println("Zi num2="+num2); } } class ExtendDemo02 { public static void main(String[] args) { // 建立子類物件 Zi z = new Zi(); // 呼叫子類中的show方法 z.show(); } } 演示結果: Fu num = 5 Zi num2 = 6
成員變數重名
如果子類父類中出現重名的成員變數,這時的訪問是有影響的。程式碼如下:
class Fu {
// Fu中的成員變數。
int num = 5;
}
class Zi extends Fu {
// Zi中的成員變數
int num = 6;
public void show() {
// 訪問父類中的num
System.out.println("Fu num=" + num);
// 訪問子類中的num
System.out.println("Zi num=" + num);
}
}
class ExtendsDemo03 {
public static void main(String[] args) {
// 建立子類物件
Zi z = new Zi();
// 呼叫子類中的show方法
z.show();
}
}
演示結果:
Fu num = 6
Zi num = 6
子父類中出現了同名的成員變數時,在子類中需要訪問父類中非私有成員變數時,需要使用super 關鍵字,修飾父類成員變數,類似於之前學過的 this 。
使用格式:
super.父類成員變數名
子類方法需要修改,程式碼如下:
class Zi extends Fu {
// Zi中的成員變數
int num = 6;
public void show() {
//訪問父類中的num
System.out.println("Fu num=" + super.num);
//訪問子類中的num
System.out.println("Zi num=" + this.num);
}
}
演示結果:
Fu num = 5
Zi num = 6
Fu 類中的成員變數是非私有的,子類中可以直接訪問。若Fu 類中的成員變數私有了,子類是不能
直接訪問的。通常編碼時,我們遵循封裝的原則,使用private修飾成員變數,那麼如何訪問父類的私有成員
變數呢?對!可以在父類中提供公共的getXxx方法和setXxx方法。
1.4 繼承後的特點——成員方法
當類之間產生了關係,其中各類中的成員方法,又產生了哪些影響呢?
成員方法不重名
如果子類父類中出現不重名的成員方法,這時的呼叫是沒有影響的。物件呼叫方法時,會先在子類中查詢有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。程式碼如下:
class Fu{
public void show(){
System.out.println("Fu類中的show方法執行");
}
}
class Zi extends Fu{
public void show2(){
System.out.println("Zi類中的show2方法執行");
}
}
public class ExtendsDemo04{
public static void main(String[] args) {
Zi z = new Zi();
//子類中沒有show方法,但是可以找到父類方法去執行
z.show();
z.show2();
}
}
成員方法重名——重寫(Override)
如果子類父類中出現重名的成員方法,這時的訪問是一種特殊情況,叫做方法重寫 (Override)。
- 方法重寫 :子類中出現與父類一模一樣的方法時(返回值型別,方法名和引數列表都相同),會出現覆蓋效果,也稱為重寫或者複寫。宣告不變,重新實現。
程式碼如下:
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子類重寫了父類的show方法
public void show() {
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args) {
Zi z = new Zi();
// 子類中有show方法,只執行重寫後的show方法
z.show(); // Zi show
}
}
重寫的應用
子類可以根據需要,定義特定於自己的行為。既沿襲了父類的功能名稱,又根據子類的需要重新實現父類方法,從而進行擴充套件增強。比如新的手機增加來電顯示頭像的功能,程式碼如下:
class Phone {
public void sendMessage(){
System.out.println("發簡訊");
}
public void call(){
System.out.println("打電話");
}
public void showNum(){
System.out.println("來電顯示號碼");
}
}
//智慧手機類
class NewPhone extends Phone {
//重寫父類的來電顯示號碼功能,並增加自己的顯示姓名和圖片功能
public void showNum(){
//呼叫父類已經存在的功能使用super
super.showNum();
//增加自己特有顯示姓名和圖片功能
System.out.println("顯示來電姓名");
System.out.println("顯示頭像");
}
}
public class ExtendsDemo06 {
public static void main(String[] args) {
// 建立子類物件
NewPhone np = new NewPhone();
// 呼叫父類繼承而來的方法
np.call();
// 呼叫子類重寫的方法
np.showNum();
}
}
這裡重寫時,用到super.父類成員方法,表示呼叫父類的成員方法。
注意事項
- 子類方法覆蓋父類方法,必須要保證許可權大於等於父類許可權。
- 子類方法覆蓋父類方法,返回值型別、函式名和引數列表都要一模一樣。
1.5 繼承後的特點——構造方法
當類之間產生了關係,其中各類中的構造方法,又產生了哪些影響呢?
首先我們要回憶兩個事情,構造方法的定義格式和作用。
- 構造方法的名字是與類名一致的。所以子類是無法繼承父類構造方法的。
- 構造方法的作用是初始化成員變數的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構
造方法中預設有一個super() ,表示呼叫父類的構造方法,父類成員變數初始化後,才可以給子類使用。代
碼如下:
class Fu {
private int n;
Fu(){
System.out.println("Fu()");
}
}
class Zi extends Fu {
Zi(){
// super(),呼叫父類構造方法
super();
System.out.println("Zi()");
}
}
public class ExtendsDemo07{
public static void main (String args[]){
Zi zi = new Zi();
}
}
輸出結果:
Fu()
Zi()
1.6 super和this
父類空間優先於子類物件產生
在每次建立子類物件時,先初始化父類空間,再建立其子類物件本身。目的在於子類物件中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。程式碼體現在子類的構造方法呼叫時,一定先呼叫父類的構造方法。理解圖解如下:
super和this的含義
- super :代表父類的儲存空間標識(可以理解為父親的引用)。
- this :代表當前物件的引用(誰呼叫就代表誰)。
super和this的用法
- 訪問成員
this.成員變數 ‐‐ 本類的
super.成員變數 ‐‐ 父類的
this.成員方法名() ‐‐ 本類的
super.成員方法名() ‐‐ 父類的
用法演示,程式碼如下:
class Animal {
public void eat() {
System.out.println("animal : eat");
}
}
class Cat extends Animal {
public void eat() {
System.out.println("cat : eat");
}
public void eatTest() {
this.eat(); // this 呼叫本類的方法
super.eat(); // super 呼叫父類的方法
}
}
public class ExtendsDemo08 {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Cat c = new Cat();
c.eatTest();
}
}
輸出結果為:
animal : eat
cat : eat
animal : eat
- 訪問構造方法
this(...) ‐‐ 本類的構造方法
super(...) ‐‐ 父類的構造方法
子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。手動呼叫父類構造會覆蓋預設的super()。super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。
1.7 繼承的特點
- Java只支援單繼承,不支援多繼承。
//一個類只能有一個父類,不可以有多個父類。
class C extends A{} //ok
class C extends A,B... //error
- Java支援多層繼承(繼承體系)。
class A{}
class B extends A{}
class C extends B{}
頂層父類是Object類。所有的類預設繼承Object,作為父類。
- 子類和父類是一種相對的概念。
第二章 抽象類
2.1 概述
由來
父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類的方法宣告和方法主體,只有宣告還有意義,而方法主體則沒有存在的意義了。我們把沒有方法主體的方法稱為抽象方法。Java語法規定,包含抽象方法的類就是抽象類。
定義
- 抽象方法 : 沒有方法體的方法。
- 抽象類:包含抽象方法的類。
2.2 abstract使用格式
抽象方法
使用abstract 關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。
定義格式:
修飾符 abstract 返回值型別 方法名 (引數列表);
程式碼舉例:
public abstract void run();
抽象類
如果一個類包含抽象方法,那麼該類必須是抽象類。
定義格式:
abstract class 類名字 {
}
程式碼舉例:
public abstract class Animal {
public abstract void run();
}
抽象的使用
繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須宣告為抽象類。最終,必須有子類實現該父類的抽象方法,否則,從最初的父類到最終的子類都不能建立物件,失去意義。
程式碼舉例:
public class Cat extends Animal {
public void run (){
System.out.println("小貓在牆頭走~~~");
}
}
public class CatTest {
public static void main(String[] args) {
// 建立子類物件
Cat c = new Cat();
// 呼叫run方法
c.run();
}
}
輸出結果:
小貓在牆頭走~~~
此時的方法重寫,是子類對父類抽象方法的完成實現,我們將這種方法重寫的操作,也叫做實現方法。
2.3 注意事項
關於抽象類的使用,以下為語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。
- 抽象類不能建立物件,如果建立,編譯無法通過而報錯。只能建立其非抽象子類的物件。
- 理解:假設建立了抽象類的物件,呼叫抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
- 抽象類中,可以有構造方法,是供子類建立物件時,初始化父類成員使用的。
- 理解:子類的構造方法中,有預設的super(),需要訪問父類構造方法。
- 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
- 理解:未包含抽象方法的抽象類,目的就是不想讓呼叫者建立該類物件,通常用於某些特殊的類結構設
計。
- 抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則,編譯無法通過而報錯。除非該子類也是抽象
類。
- 理解:假設不重寫所有抽象方法,則類中可能包含抽象方法。那麼建立物件後,呼叫抽象的方法,沒有
意義。
第三章 繼承的綜合案例
3.1 綜合案例:群主發普通紅包
群主發普通紅包。某群有多名成員,群主給成員發普通紅包。普通紅包的規則:
- 群主的一筆金額,從群主餘額中扣除,平均分成n等份,讓成員領取。
- 成員領取紅包後,儲存到成員餘額中。
請根據描述,完成案例中所有類的定義以及指定類之間的繼承關係,並完成發紅包的操作。
3.2 案例分析
根據描述分析,得出如下繼承體系:
- 群主,發紅包 - 繼承 -> User 使用者名稱 餘額 展示資訊()
- 成員,開紅包 - 繼承 -> User 使用者名稱 餘額 展示資訊()
3.3 案例實現
定義使用者類:
public class User {
// 成員變數
private String username; // 使用者名稱
private double leftMoney; // 餘額
// 構造方法
public User() { }
public User(String username, double leftMoney) {
this.username = username;
this.leftMoney = leftMoney;
}
// get/set方法
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public double getLeftMoney() {
return leftMoney;
}
public void setLeftMoney(double leftMoney) {
this.leftMoney = leftMoney;
}
// 展示資訊的方法
public void show() {
System.out.println("使用者名稱:"+ username +" , 餘額為:" + leftMoney + "元");
}
}
定義群主類:
public class QunZhu extends User {
// 新增構造方法
public QunZhu() {
}
public QunZhu(String username, double leftMoney) {
// 通過super 呼叫父類構造方法
super(username, leftMoney);
}
/*
群主發紅包,就是把一個整數的金額,分層若干等份。
1.獲取群主餘額,是否夠發紅包.
不能則返回null,並提示.
能則繼續.
2.修改群主餘額.
3.拆分紅包.
3.1.如果能整除,那麼就平均分。
3.2.如果不能整除,那麼就把餘數分給最後一份。
*/
public ArrayList<Double> send(int money, int count) {
// 獲取群主餘額
double leftMoney = getLeftMoney();
if(money > leftMoney) {
return null;
}
// 修改群主餘額的
setLeftMoney(leftMoney ‐ money);
// 建立一個集合,儲存等份金額
ArrayList<Double> list = new ArrayList<>();
// 擴大100倍,相當於折算成'分'為單位,避免小數運算損失精度的問題
money = money * 100;
// 每份的金額
int m = money / count;
// 不能整除的餘數
int l = money % count;
// 無論是否整除,n‐1份,都是每份的等額金額
for (int i = 0; i < count ‐ 1; i++) {
// 縮小100倍,折算成 '元'
list.add(m / 100.0);
}
// 判斷是否整除
if (l == 0) {
// 能整除, 最後一份金額,與之前每份金額一致
list.add(m / 100.0);
} else {
// 不能整除, 最後一份的金額,是之前每份金額+餘數金額
list.add((m + l) / 100.00);
}
// 返回集合
return list;
}
}
定義成員類:
public class Member extends User {
public Member() {
}
public Member(String username, double leftMoney) {
super(username, leftMoney);
}
// 開啟紅包,就是從集合中,隨機取出一份,儲存到自己的餘額中
public void openHongbao(ArrayList<Double> list) {
// 建立Random物件
Random r = new Random();
// 隨機生成一個角標
int index = r.nextInt(list.size());
// 移除一個金額
Double money = list.remove(index);
// 直接呼叫父類方法,設定到餘額
setLeftMoney( money );
}
}
定義測試類:
public class Test {
public static void main(String[] args) {
// 建立一個群主物件
QunZhu qz = new QunZhu("群主" , 200);
// 建立一個鍵盤錄入
Scanner sc = new Scanner();
System.out.println("請輸入金額:");
int money = sc.nextInt();
System.out.println("請輸入個數:");
int count = sc.nextInt();
// 傳送紅包
ArrayList<Double> sendList = s.send(money,count);
// 判斷,如果餘額不足
if(sendList == null){
System.out.println(" 餘額不足...");
return;
}
// 建立三個成員
Member m = new Member();
Member m2 = new Member();
Member m3 = new Member();
// 開啟紅包
m.openHongbao(sendList);
m2.openHongbao(sendList);
m3.openHongbao(sendList);
// 展示資訊
qz.show();
m.show();
m2.show();
m3.show();
}
}