第一章繼承
1.1概述
由來
多個(gè)類中存在相同屬性和行為時(shí),將這些內(nèi)容抽取到單獨(dú)一個(gè)類中,那么多個(gè)類無需再定義這些屬性和行為,只要繼承那一個(gè)類即可。如圖所示:

其中,多個(gè)類可以稱為子類,單獨(dú)那一個(gè)類稱為父類、超類(superclass)或者基類。
繼承描述的是事物之間的所屬關(guān)系,這種關(guān)系是:is-a的關(guān)系。例如,圖中兔子屬于食草動(dòng)物,食草動(dòng)物屬于動(dòng)物??梢?,父類更通用,子類更具體。我們通過繼承,可以使多種事物之間形成一種關(guān)系體系。
定義
- 繼承:就是子類繼承父類的屬性和行為,使得子類對(duì)象具有與父類相同的屬性、相同的行為。子類可以直接 訪問父類中的非私有的屬性和行為。
好處
- 提高代碼的復(fù)用性。
- 類與類之間產(chǎn)生了關(guān)系,是多態(tài)的前提。
1.2 繼承的格式
通過extends關(guān)鍵字,可以聲明一個(gè)子類繼承另外一個(gè)父類,定義格式如下:
class 父類 {
...
}
class 子類 extends 父類 {
...
}
繼承演示,代碼如下:
/*
*定義員工類Employee,做為父類
*/
class Employee {
String name; // 定義 name 屬性
// 定義員工的工作方法
public void work() {
System.out. println("盡心盡力地工作");
}
}
/*
*定義講師類Teacher繼承 員工類Employee
*/
class Teacher extends Employee {
//定義一個(gè)打印name的方法
public void printName() {
System.out.println("name=" + name);
}
}
/*
* 定義測(cè)試類
*/
public class ExtendDemo01 {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)講師類對(duì)象
Teacher t = new Teacher();
//為該員工類的name屬性進(jìn)行賦值
t.name = "小明";
//調(diào)用該員工的printName()方法
t.printName(); // name = 小明
//調(diào)用Teacher類繼承來的work()方法
t.work(); // 盡心盡力地工作
}
}
1.3 繼承后的特點(diǎn)——成員變量
當(dāng)類之間產(chǎn)生了關(guān)系后,其中各類中的成員變量,又產(chǎn)生了哪些影響呢?
成員變量不重名
如果子類父類中出現(xiàn)不重名的成員變量,這時(shí)的訪問是沒有影響的。代碼如下:
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) {
// 創(chuàng)建子類對(duì)象
Zi z = new Zi();
//調(diào)用子類中的show方法
z.show();
}
}
演示結(jié)果:
Fu num = 5
Zi num2 = 6
成員變量重名
如果子類父類中出現(xiàn)重名的成員變量,這時(shí)的訪問是有影響的。代碼如下:
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) {
// 創(chuàng)建子類對(duì)象
Zi z = new Zi();
//調(diào)用子類中的show方法
z.show();
}
}
演示結(jié)果:
Fu num = 6
Zi num = 6?
子父類中出現(xiàn)了同名的成員變量時(shí),在子類中需要訪問父類中非私有成員變量時(shí),需要使用super關(guān)鍵字,修飾父類成員變量,類以于之前學(xué)過的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);
}
}
演示結(jié)果:
Fu num = 5
Zi num = 6
小貼士: Fu類中的成員變量是非私有的,子類中可以直接訪問。若Fu類中的成員變量私有了,子類是不能直接訪問的。通常編碼時(shí),我們遵循封裝的原則,使用private修飾成員變量,那么如何訪問父類的私有成員 變量呢?對(duì)!可以在父類中提供公共的getXxx方法和setXxx方法。
1.4繼承后的特點(diǎn)一一成員方法
當(dāng)類之間產(chǎn)生了關(guān)系,其中各類中的成員方法,又產(chǎn)生了哪些影響呢?
成員方法不重名
如果子類父類中出現(xiàn)不重名的成員方法,這時(shí)的調(diào)用是沒有影響的。對(duì)象調(diào)用方法時(shí),會(huì)先在子類中查找有沒有對(duì)應(yīng)的方法,若子類中存在就會(huì)執(zhí)行子類中的方法,若子類中不存在就會(huì)執(zhí)行父類中相應(yīng)的方法。代碼如下:
class Fu{
public void show(){
System.out. println("Fu類中的show方法執(zhí)行");
}
}
class Zi extends Fu{
public void show2(){
System.out. println("Zi類中的show2方法執(zhí)行");
}
}
public class ExtendsDemo04{
public static void main(String[] args) {
Zi z = new Zi();
//子類中沒有show方法,但是可以找到父類方法去執(zhí)行?
z.show();
z.show2();
}
}
成員方法重名——重寫(Override)
如果子類父類中出現(xiàn)重名的成員方法,這時(shí)的訪問是一種特殊情況,叫做**方法重寫 **(Override)。
- 方法重寫:子類中出現(xiàn)與父類一模一樣的方法時(shí)(返回值類型,方法名和參數(shù)列表都相同),會(huì)出現(xiàn)覆蓋效果,也稱為重寫或者復(fù)寫。聲明不變,重新實(shí)現(xiàn)。
代碼如下:
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方法,只執(zhí)行重寫后的show方法
z.show(); // Zi show
}
}
重寫的應(yīng)用
子類可以根據(jù)需要,定義特定于自己的行為。既沿襲了父類的功能名稱,又根據(jù)子類的需要重新實(shí)現(xiàn)父類方法,從 而進(jìn)行擴(kuò)展增強(qiáng)。比如新的手機(jī)增加來電顯示頭像的功能,代碼如下:
class Phone {
public void sendMessage(){
System.out.println ("發(fā)短信");
}
public void call(){
System.out.println ("打電話");
}
public void showNum(){
System.out.println("來電顯示號(hào)碼");
}
}
//智能手機(jī)類
class NewPhone extends Phone {
//重寫父類的來電顯示號(hào)碼功能,并增加自己的顯示姓名和圖片功能
public void showNum(){
//調(diào)用父類已經(jīng)存在的功能使用super
super.showNum();
//增加自己特有顯示姓名和圖片功能
System. out. println ("顯示來電姓名");
System. out. println ("顯示頭像");
}
}
public class ExtendsDemo06 {
public static void main(String[] args) {
// 創(chuàng)建子類對(duì)象
NewPhone np = new NewPhone();
// 調(diào)用父類繼承而來的方法
np.call();
// 調(diào)用子類重寫的方法
np.showNum();
}
}
小貼士:這里重寫時(shí),用到super?父類成員方法,表示調(diào)用父類的成員方法。
注意事項(xiàng)
- 子類方法覆蓋父類方法,必須要保證權(quán)限大于等于父類權(quán)限。
- 子類方法覆蓋父類方法,返回值類型、函數(shù)名和參數(shù)列表都要一模一樣。
1.5 繼承后的特點(diǎn)——構(gòu)造方法
當(dāng)類之間產(chǎn)生了關(guān)系,其中各類中的構(gòu)造方法,又產(chǎn)生了哪些影響呢? 首先我們要回憶兩個(gè)事情,構(gòu)造方法的定義格式和作用。
- 構(gòu)造方法的名字是與類名一致的。所以子類是無法繼承父類構(gòu)造方法的。
- 構(gòu)造方法的作用是初始化成員變量的。所以子類的初始化過程中,必須先執(zhí)行父類的初始化動(dòng)作。子類的構(gòu)造方法中默認(rèn)有一個(gè)super(),表示調(diào)用父類的構(gòu)造方法,父類成員變量初始化后,才可以給子類使用。代碼如下:
class Fu {
private int n;
Fu(){
System.out.println("Fu()");
}
}
class Zi extends Fu {
Zi(){
// super (),調(diào)用父類構(gòu)造方法
super();
System.out.println("Zi() ");
}
}
public class ExtendsDemo07{
public static void main (String args[]){
Zi zi = new Zi();
}
}
輸出結(jié)果:
Fu()
Zi()
1.6 super 和 this
父類空間優(yōu)先于子類對(duì)象產(chǎn)生
在每次創(chuàng)建子類對(duì)象時(shí),先初始化父類空間,再創(chuàng)建其子類對(duì)象本身。目的在于子類對(duì)象中包含了其對(duì)應(yīng)的父類空 間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。代碼體現(xiàn)在子類的構(gòu) 造方法調(diào)用時(shí),一定先調(diào)用父類的構(gòu)造方法。理解圖解如下:

super和this的含義
- super :代表父類的存儲(chǔ)空間標(biāo)識(shí)(可以理解為父親的引用)。
- this:代表當(dāng)前對(duì)象的引用(誰(shuí)調(diào)用就代表誰(shuí))。
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 調(diào)用本類的方法
super.eat(); // super 調(diào)用父類的方法
}
}
class ExtendsDemo08 {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Cat c = new Cat();
c.eatTest();
}
}
輸出結(jié)果為:
animal : eat
cat : eat
animal : eat
- 訪問構(gòu)造方法
this(...) -- 本類的構(gòu)造方法
super(...) -- 父類的構(gòu)造方法
子類的每個(gè)構(gòu)造方法中均有默認(rèn)的super(),調(diào)用父類的空參構(gòu)造。手動(dòng)調(diào)用父類構(gòu)造會(huì)覆蓋默認(rèn)的super()。
super() 和 this() 都必須是在構(gòu)造方法的第一行,所以不能同時(shí)出現(xiàn)。
1.7 繼承的特點(diǎn)
- Java只支持單繼承,不支持多繼承。
//一個(gè)類只能有一個(gè)父類,不可以有多個(gè)父類。
class C extends A{} //ok
class C extends A,B... //error
- Java支持多層繼承(繼承體系)。
class A{}
class B extends A{}
class C extends B{}
頂層父類是Object類。所有的類默認(rèn)繼承Object,作為父類。
- 子類和父類是一種相對(duì)的概念。
第二章 抽象類
2.1概述
由來
父類中的方法,被它的子類們重寫,子類各自的實(shí)現(xiàn)都不盡相同。那么父類的方法聲明和方法主體,只有聲明還有 意義,而方法主體則沒有存在的意義了。我們把沒有方法主體的方法稱為抽象方法。Java語(yǔ)法規(guī)定,包含抽象方法的類就是抽象類。
定義
- 抽象方法:沒有方法體的方法。
- 抽象類:包含抽象方法的類。
2.2 abstract使用格式
抽象方法
使用abstract關(guān)鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個(gè)方法名,而沒有方法體。
定義格式:
修飾符 abstract 返回值類型 方法名(參數(shù)列表);
代碼舉例:
public abstract void run() ;
抽象類
如果一個(gè)類包含抽象方法,那么該類必須是抽象類。
定義格式:
abstract class 類名字 {
}
代碼舉例:
public abstract class Animal {
public abstract void run();
}
抽象的使用
繼承抽象類的子類必須重寫父類所有的抽象方法。否則,該子類也必須聲明為抽象類。最終,必須有子類實(shí)現(xiàn)該父類的抽象方法,否則,從最初的父類到最終的子類都不能創(chuàng)建對(duì)象,失去意義。
代碼舉例:
public class Cat extends Animal {
public void run (){
System.out.println(" 小貓?jiān)趬︻^走 ~~~") ;
}
}
public class CatTest {
public static void main(String[] args) {
// 創(chuàng)建子類對(duì)象
Cat c = new Cat();
// 調(diào)用 run 方法
c.run();
}
}
輸出結(jié)果:
小貓?jiān)趬︻^走~~~
此時(shí)的方法重寫,是子類對(duì)父類抽象方法的完成實(shí)現(xiàn),我們將這種方法重寫的操作,也叫做實(shí)現(xiàn)方法。
2.3 注意事項(xiàng)
關(guān)于抽象類的使用,以下為語(yǔ)法上要注意的細(xì)節(jié),雖然條目較多,但若理解了抽象的本質(zhì),無需死記硬背。
- 抽象類不能創(chuàng)建對(duì)象,如果創(chuàng)建,編譯無法通過而報(bào)錯(cuò)。只能創(chuàng)建其非抽象子類的對(duì)象。
理解:假設(shè)創(chuàng)建了抽象類的對(duì)象,調(diào)用抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
- 抽象類中,可以有構(gòu)造方法,是供子類創(chuàng)建對(duì)象時(shí),初始化父類成員使用的。
理解:子類的構(gòu)造方法中,有默認(rèn)的super(),需要訪問父類構(gòu)造方法。
- 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
理解:未包含抽象方法的抽象類,目的就是不想讓調(diào)用者創(chuàng)建該類對(duì)象,通常用于某些特殊的類結(jié)構(gòu)設(shè)計(jì)。
- 抽象類的子類,必須重寫抽象父類中所有的抽象方法,否則,編譯無法通過而報(bào)錯(cuò)。除非該子類也是抽象類。
理解:假設(shè)不重寫所有抽象方法,則類中可能包含抽象方法。那么創(chuàng)建對(duì)象后,調(diào)用抽象的方法,沒有意義。
第三章 繼承的綜合案例
3.1 綜合案例:群主發(fā)普通紅包
群主發(fā)普通紅包。某群有多名成員,群主給成員發(fā)普通紅包。普通紅包的規(guī)則:
- 群主的一筆金額,從群主余額中扣除,平均分成n等份,讓成員領(lǐng)取。
- 成員領(lǐng)取紅包后,保存到成員余額中。
請(qǐng)根據(jù)描述,完成案例中所有類的定義以及指定類之間的繼承關(guān)系,并完成發(fā)紅包的操作。
3.2 案例分析
根據(jù)描述分析,得出如下繼承體系:

3.3 案例實(shí)現(xiàn)
定義用戶類:
public class User {
// 成員變量
private String username; // 用戶名
private double leftMoney; // 余額
// 構(gòu)造方法
public User() { }
public User(String username, double leftMoney) {
this.username = username;
this.leftMoney = leftMoney;
}
// get/se t 方法
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 {
// 添加構(gòu)造方法
public QunZhu() {
}
public QunZhu(String username, double leftMoney) {
//通過super調(diào)用父類構(gòu)造方法
super(username, leftMoney);
}
/*
群主發(fā)紅包,就是把一個(gè)整數(shù)的金額,分層若干等份。
1. 獲取群主余額,是否夠發(fā)紅包.
不能則返回null,并提示.
能則繼續(xù).
2. 修改群主余額.
3. 拆分紅包.
3.1 如果能整除,那么就平均分。
3.2 如果不能整除,那么就把余數(shù)分給最后一份。
*/
public ArrayList<Double> send(int money, int count) {
// 獲取群主余額
double leftMoney = getLeftMoney();
if(money > leftMoney) {?
return null;
}
// 修改群主余額的
setLeftMoney(leftMoney - money);
// 創(chuàng)建一個(gè)集合 , 保存等份金額
ArrayList<Double> list = new ArrayList<>();
// 擴(kuò)大100倍,相當(dāng)于折算成'分' 為單位,避免小數(shù)運(yùn)算損失精度的問題
money = money * 100;
// 每份的金額
int m = money / count;
// 不能整除的余數(shù)
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 {
// 不能整除, 最后一份的金額,是之前每份金額+余數(shù)金額
list.add((m + 1) / 100.00);
}
// 返回集合
return list;
}
}
定義成員類:
public class Member extends User {
public Member() {
}
public Member(String username, double leftMoney) {
super(username, leftMoney);
}
// 打開紅包,就是從集合中,隨機(jī)取出一份,保存到自己的余額中
public void openHongbao(ArrayList<Double> list) {
//創(chuàng)建Random對(duì)象
Random r = new Random();
// 隨機(jī)生成一個(gè)角標(biāo)
int index = r.nextInt(list.size());
// 移除一個(gè)金額?
Double money = list.remove(index); // 直接調(diào)用父類方法,設(shè)置到余額
setLeftMoney( money );
}
}
定義測(cè)試類:
public class Test {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)群主對(duì)象
QunZhu qz = new QunZhu("群主",200);
// 創(chuàng)建一個(gè)鍵盤錄入
Scanner sc = new Scanner();
System.out.println ("請(qǐng)輸入金額:");
int money = sc.nextInt();
System.out.println("請(qǐng)輸入個(gè)數(shù):");
int count = sc.nextInt();
// 發(fā)送紅包
ArrayList<Double> sendList = s.send(money,count);
// 判斷 , 如果余額不足
if(sendList == null){
System.out.println(" 余額不足...");
return;
}
// 創(chuàng)建三個(gè)成員
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();
}
}
課后請(qǐng)同學(xué)自己思考并完成擴(kuò)展需求。
案例擴(kuò)展:
- 如果成員的余額不為0呢,將如何處理?
- 如果群主想輸入帶小數(shù)的金額呢,將如何處理?