第一章繼承
1.1概述
由來
多個類中存在相同屬性和行為時带兜,將這些內(nèi)容抽取到單獨一個類中氛什,那么多個類無需再定義這些屬性和行為赫舒,只要繼承那一個類即可。如圖所示:
其中,多個類可以稱為子類辱姨,單獨那一個類稱為父類、超類(superclass)或者基類辽装。
繼承描述的是事物之間的所屬關(guān)系参袱,這種關(guān)系是:is-a的關(guān)系。例如浑彰,圖中兔子屬于食草動物恭理,食草動物屬于動物」洌可見颜价,父類更通用,子類更具體诉濒。我們通過繼承周伦,可以使多種事物之間形成一種關(guān)系體系。
定義
- 繼承:就是子類繼承父類的屬性和行為未荒,使得子類對象具有與父類相同的屬性专挪、相同的行為。子類可以直接 訪問父類中的非私有的屬性和行為茄猫。
好處
- 提高代碼的復(fù)用性狈蚤。
- 類與類之間產(chǎn)生了關(guān)系,是多態(tài)的前提划纽。
1.2 繼承的格式
通過extends關(guān)鍵字脆侮,可以聲明一個子類繼承另外一個父類,定義格式如下:
class 父類 {
...
}
class 子類 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) {
// 創(chuàng)建一個講師類對象
Teacher t = new Teacher();
//為該員工類的name屬性進行賦值
t.name = "小明";
//調(diào)用該員工的printName()方法
t.printName(); // name = 小明
//調(diào)用Teacher類繼承來的work()方法
t.work(); // 盡心盡力地工作
}
}
1.3 繼承后的特點——成員變量
當類之間產(chǎn)生了關(guān)系后,其中各類中的成員變量比默,又產(chǎn)生了哪些影響呢幻捏?
成員變量不重名
如果子類父類中出現(xiàn)不重名的成員變量,這時的訪問是沒有影響的命咐。代碼如下:
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)建子類對象
Zi z = new Zi();
//調(diào)用子類中的show方法
z.show();
}
}
演示結(jié)果:
Fu num = 5
Zi num2 = 6
成員變量重名
如果子類父類中出現(xiàn)重名的成員變量榛臼,這時的訪問是有影響的伊佃。代碼如下:
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)建子類對象
Zi z = new Zi();
//調(diào)用子類中的show方法
z.show();
}
}
演示結(jié)果:
Fu num = 6
Zi num = 6?
子父類中出現(xiàn)了同名的成員變量時沛善,在子類中需要訪問父類中非私有成員變量時航揉,需要使用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類中的成員變量私有了,子類是不能直接訪問的抵屿。通常編碼時庆锦,我們遵循封裝的原則,使用private修飾成員變量轧葛,那么如何訪問父類的私有成員 變量呢搂抒?對!可以在父類中提供公共的getXxx方法和setXxx方法尿扯。
1.4繼承后的特點一一成員方法
當類之間產(chǎn)生了關(guān)系求晶,其中各類中的成員方法,又產(chǎn)生了哪些影響呢衷笋?
成員方法不重名
如果子類父類中出現(xiàn)不重名的成員方法芳杏,這時的調(diào)用是沒有影響的。對象調(diào)用方法時辟宗,會先在子類中查找有沒有對應(yīng)的方法爵赵,若子類中存在就會執(zhí)行子類中的方法,若子類中不存在就會執(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)重名的成員方法,這時的訪問是一種特殊情況缩挑,叫做**方法重寫 **(Override)但两。
- 方法重寫:子類中出現(xiàn)與父類一模一樣的方法時(返回值類型,方法名和參數(shù)列表都相同)供置,會出現(xiàn)覆蓋效果谨湘,也稱為重寫或者復(fù)寫。聲明不變,重新實現(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ù)子類的需要重新實現(xiàn)父類方法赤拒,從 而進行擴展增強秫筏。比如新的手機增加來電顯示頭像的功能,代碼如下:
class Phone {
public void sendMessage(){
System.out.println ("發(fā)短信");
}
public void call(){
System.out.println ("打電話");
}
public void showNum(){
System.out.println("來電顯示號碼")挎挖;
}
}
//智能手機類
class NewPhone extends Phone {
//重寫父類的來電顯示號碼功能这敬,并增加自己的顯示姓名和圖片功能
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)建子類對象
NewPhone np = new NewPhone();
// 調(diào)用父類繼承而來的方法
np.call();
// 調(diào)用子類重寫的方法
np.showNum();
}
}
小貼士:這里重寫時蕉朵,用到super?父類成員方法崔涂,表示調(diào)用父類的成員方法。
注意事項
- 子類方法覆蓋父類方法始衅,必須要保證權(quán)限大于等于父類權(quán)限冷蚂。
- 子類方法覆蓋父類方法,返回值類型汛闸、函數(shù)名和參數(shù)列表都要一模一樣蝙茶。
1.5 繼承后的特點——構(gòu)造方法
當類之間產(chǎn)生了關(guān)系,其中各類中的構(gòu)造方法诸老,又產(chǎn)生了哪些影響呢隆夯? 首先我們要回憶兩個事情,構(gòu)造方法的定義格式和作用别伏。
- 構(gòu)造方法的名字是與類名一致的蹄衷。所以子類是無法繼承父類構(gòu)造方法的。
- 構(gòu)造方法的作用是初始化成員變量的厘肮。所以子類的初始化過程中愧口,必須先執(zhí)行父類的初始化動作。子類的構(gòu)造方法中默認有一個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)先于子類對象產(chǎn)生
在每次創(chuàng)建子類對象時恬涧,先初始化父類空間,再創(chuàng)建其子類對象本身碴巾。目的在于子類對象中包含了其對應(yīng)的父類空 間溯捆,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員提揍。代碼體現(xiàn)在子類的構(gòu) 造方法調(diào)用時啤月,一定先調(diào)用父類的構(gòu)造方法。理解圖解如下:
super和this的含義
- super :代表父類的存儲空間標識(可以理解為父親的引用)劳跃。
- this:代表當前對象的引用(誰調(diào)用就代表誰)谎仲。
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òu)造方法中均有默認的super(),調(diào)用父類的空參構(gòu)造祈搜。手動調(diào)用父類構(gòu)造會覆蓋默認的super()较店。
super() 和 this() 都必須是在構(gòu)造方法的第一行,所以不能同時出現(xiàn)夭问。
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概述
由來
父類中的方法,被它的子類們重寫,子類各自的實現(xiàn)都不盡相同仔涩。那么父類的方法聲明和方法主體,只有聲明還有 意義,而方法主體則沒有存在的意義了粘舟。我們把沒有方法主體的方法稱為抽象方法熔脂。Java語法規(guī)定,包含抽象方法的類就是抽象類柑肴。
定義
- 抽象方法:沒有方法體的方法霞揉。
- 抽象類:包含抽象方法的類。
2.2 abstract使用格式
抽象方法
使用abstract關(guān)鍵字修飾方法晰骑,該方法就成了抽象方法适秩,抽象方法只包含一個方法名,而沒有方法體。
定義格式:
修飾符 abstract 返回值類型 方法名(參數(shù)列表);
代碼舉例:
public abstract void run() 秽荞;
抽象類
如果一個類包含抽象方法骤公,那么該類必須是抽象類。
定義格式:
abstract class 類名字 {
}
代碼舉例:
public abstract class Animal {
public abstract void run()扬跋;
}
抽象的使用
繼承抽象類的子類必須重寫父類所有的抽象方法阶捆。否則,該子類也必須聲明為抽象類钦听。最終趁猴,必須有子類實現(xiàn)該父類的抽象方法,否則彪见,從最初的父類到最終的子類都不能創(chuàng)建對象,失去意義娱挨。
代碼舉例:
public class Cat extends Animal {
public void run (){
System.out.println(" 小貓在墻頭走 ~~~") 余指;
}
}
public class CatTest {
public static void main(String[] args) {
// 創(chuàng)建子類對象
Cat c = new Cat();
// 調(diào)用 run 方法
c.run();
}
}
輸出結(jié)果:
小貓在墻頭走~~~
此時的方法重寫,是子類對父類抽象方法的完成實現(xiàn)跷坝,我們將這種方法重寫的操作酵镜,也叫做實現(xiàn)方法。
2.3 注意事項
關(guān)于抽象類的使用柴钻,以下為語法上要注意的細節(jié)淮韭,雖然條目較多,但若理解了抽象的本質(zhì)贴届,無需死記硬背靠粪。
- 抽象類不能創(chuàng)建對象,如果創(chuàng)建毫蚓,編譯無法通過而報錯占键。只能創(chuàng)建其非抽象子類的對象。
理解:假設(shè)創(chuàng)建了抽象類的對象元潘,調(diào)用抽象的方法畔乙,而抽象方法沒有具體的方法體,沒有意義翩概。
- 抽象類中牲距,可以有構(gòu)造方法,是供子類創(chuàng)建對象時钥庇,初始化父類成員使用的牍鞠。
理解:子類的構(gòu)造方法中,有默認的super()上沐,需要訪問父類構(gòu)造方法皮服。
- 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類龄广。
理解:未包含抽象方法的抽象類硫眯,目的就是不想讓調(diào)用者創(chuàng)建該類對象,通常用于某些特殊的類結(jié)構(gòu)設(shè)計择同。
- 抽象類的子類两入,必須重寫抽象父類中所有的抽象方法,否則敲才,編譯無法通過而報錯裹纳。除非該子類也是抽象類。
理解:假設(shè)不重寫所有抽象方法紧武,則類中可能包含抽象方法剃氧。那么創(chuàng)建對象后,調(diào)用抽象的方法阻星,沒有意義朋鞍。
第三章 繼承的綜合案例
3.1 綜合案例:群主發(fā)普通紅包
群主發(fā)普通紅包。某群有多名成員妥箕,群主給成員發(fā)普通紅包滥酥。普通紅包的規(guī)則:
- 群主的一筆金額,從群主余額中扣除畦幢,平均分成n等份坎吻,讓成員領(lǐng)取。
- 成員領(lǐng)取紅包后宇葱,保存到成員余額中瘦真。
請根據(jù)描述,完成案例中所有類的定義以及指定類之間的繼承關(guān)系贝搁,并完成發(fā)紅包的操作吗氏。
3.2 案例分析
根據(jù)描述分析,得出如下繼承體系:
3.3 案例實現(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ā)紅包雷逆,就是把一個整數(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)建一個集合 , 保存等份金額
ArrayList<Double> list = new ArrayList<>();
// 擴大100倍,相當于折算成'分' 為單位,避免小數(shù)運算損失精度的問題
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);
}
// 打開紅包,就是從集合中,隨機取出一份,保存到自己的余額中
public void openHongbao(ArrayList<Double> list) {
//創(chuàng)建Random對象
Random r = new Random();
// 隨機生成一個角標
int index = r.nextInt(list.size());
// 移除一個金額?
Double money = list.remove(index); // 直接調(diào)用父類方法,設(shè)置到余額
setLeftMoney( money );
}
}
定義測試類:
public class Test {
public static void main(String[] args) {
// 創(chuàng)建一個群主對象
QunZhu qz = new QunZhu("群主",200);
// 創(chuàng)建一個鍵盤錄入
Scanner sc = new Scanner();
System.out.println ("請輸入金額:");
int money = sc.nextInt();
System.out.println("請輸入個數(shù):")仿村;
int count = sc.nextInt();
// 發(fā)送紅包
ArrayList<Double> sendList = s.send(money,count);
// 判斷 , 如果余額不足
if(sendList == null){
System.out.println(" 余額不足...");
return;
}
// 創(chuàng)建三個成員
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();
}
}
課后請同學(xué)自己思考并完成擴展需求。
案例擴展:
- 如果成員的余額不為0呢兴喂,將如何處理蔼囊?
- 如果群主想輸入帶小數(shù)的金額呢焚志,將如何處理?