類與類之間的三種關(guān)系
類與類之間有三種關(guān)系
is a:繼承關(guān)系艘包,例如:公共汽車is a汽車
use a:使用關(guān)系,例如:人 use a 鉗子
has a:包含關(guān)系寨典,例如:人has a 胳膊
繼承的好處
- 繼承的出現(xiàn)提高了代碼的復(fù)用性熏迹,提高軟件開發(fā)的效率
- 繼承的出現(xiàn)讓類與類之間產(chǎn)生了關(guān)系,提供了多態(tài)的前提
繼承的定義格式
在程序中凝赛,如果想申明一個(gè)類繼承另一個(gè)類注暗,需要使用extends關(guān)鍵字
class 子類 extends 父類 {
}
使用繼承
案例:公司有2個(gè)部門坛缕,人事部和研發(fā)部,各自屬性如下
定義人事部類
public class PersonalDepartment {
private int ID;// 部門編號(hào)
private String name = "待定";// 部門名稱
private int amount = 0;// 部門人數(shù)
private String responsibility = "待定";// 部門職責(zé)
private String manager = "無名氏";// 部門經(jīng)理
private int count;//招聘人數(shù)
public PersonalDepartment() {
}
public PersonalDepartment(int ID, String name, int amount, String responsibility, String manager, int count) {
this.ID = ID;
this.name = name;
this.amount = amount;
this.responsibility = responsibility;
this.manager = manager;
this.count = count;
}
定義研發(fā)部
/**
* 研發(fā)部
*/
public class ResourceDepartment {
private int ID;// 部門編號(hào)
private String name = "待定";// 部門名稱
private int amount = 0;// 部門人數(shù)
private String responsibility = "待定";// 部門職責(zé)
private String manager = "無名氏";// 部門經(jīng)理
private String speciality =null;//研發(fā)方向
public ResourceDepartment() {
}
public ResourceDepartment(int ID, String name, int amount, String responsibility, String manager, String speciality) {
this.ID = ID;
this.name = name;
this.amount = amount;
this.responsibility = responsibility;
this.manager = manager;
this.speciality = speciality;
}
......
問題:兩個(gè)類中的屬性有相同的部分捆昏,代碼冗余赚楚。解決辦法是將兩個(gè)部門共性的屬性抽取出來,放在父類中骗卜,然后讓兩個(gè)部門繼承父類宠页。
定義父類:兩個(gè)部門共性的屬性抽取到父類中
/** 部門父類:存放所有子類共性的內(nèi)容 */public class Deparment { private int ID;// 部門編號(hào) private String name = "待定";// 部門名稱 private int amount = 0;// 部門人數(shù) private String responsibility = "待定";// 部門職責(zé) private String manager = "無名氏";// 部門經(jīng)理
子類修改如下:
- 共性的屬性刪除
- 共性屬性的get和set方法刪除
- 構(gòu)造方法做調(diào)整
/**
* 人事部
*/
public class PersonalDepartment {
private int count;//招聘人數(shù)
public PersonalDepartment() {
}
public PersonalDepartment(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
/**
* 研發(fā)部
*/
public class ResourceDepartment {
private String speciality =null;//研發(fā)方向
public ResourceDepartment() {
}
public ResourceDepartment(String speciality) {
this.speciality = speciality;
}
public String getSpeciality() {
return speciality;
}
public void setSpeciality(String speciality) {
this.speciality = speciality;
}
}
讓人事部和研發(fā)部繼承部門的父類
- 使用extends Deparment繼承部門類
- 人事部
public class PersonalDepartment extends Deparment{
private int count;//招聘人數(shù)
public PersonalDepartment() {
}
public PersonalDepartment(int count) {
this.count = count;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
* 使用extends Deparment繼承部門類
/**
* 研發(fā)部
*/
public class ResourceDepartment extends Deparment{
private String speciality =null;//研發(fā)方向
public ResourceDepartment() {
}
public ResourceDepartment(String speciality) {
this.speciality = speciality;
}
public String getSpeciality() {
return speciality;
}
public void setSpeciality(String speciality) {
this.speciality = speciality;
}
}
驗(yàn)證子類是否真的從父類繼承了數(shù)據(jù)
驗(yàn)證方法:
- 創(chuàng)建人事部和研發(fā)部的對(duì)象,如果能夠調(diào)用出父類的方法寇仓,證明繼承了
創(chuàng)建一個(gè)公司類举户,測(cè)試一下:
/**
* 公司類
*/
public class Company {
public static void main(String[] args) {
//創(chuàng)建人事部對(duì)象
PersonalDepartment personalDepartment = new PersonalDepartment();
//創(chuàng)建研發(fā)部對(duì)象
ResourceDepartment resourceDepartment = new ResourceDepartment();
//調(diào)用父類的方法
System.out.println(personalDepartment.getName()); //getName()是父類的
System.out.println(resourceDepartment.getName()); //getName()是父類的
}
}
如上:
- 實(shí)現(xiàn)代碼復(fù)用
- 父類可以控制子類能繼承什么,通過封裝控制
注意:對(duì)象的屬性可以通過方法賦值
創(chuàng)建子類對(duì)象時(shí)的運(yùn)行規(guī)則
- 第一個(gè)方面:構(gòu)造函數(shù)的調(diào)用順序問題
- new子類時(shí)遍烦,首先調(diào)用子類構(gòu)造函數(shù)俭嘁,但是不執(zhí)行子類構(gòu)造函數(shù)
- 子類構(gòu)造函數(shù)被調(diào)用后立即調(diào)用父類的構(gòu)造函數(shù)。
- 第二個(gè)方面:屬性初始化順序的問題
- 從Object類開始初始化
- 然后依次從父到子的順序初始化(哪個(gè)類定義的屬性服猪,就由哪個(gè)類負(fù)責(zé)初始化)
子類通過關(guān)鍵字super()調(diào)用父類的構(gòu)造函數(shù)
- super()必須放在子類構(gòu)造函數(shù)的第一行代碼
繼承的注意事項(xiàng)
類只支持單繼承供填,不允許多繼承
class A{}
class B{}
class C extends A,B{} // C類不可以同時(shí)繼承A類和B類
多個(gè)類可以繼承一個(gè)父類
class A{}
class B extends A{}
class C extends A{} // 類B和類C都可以繼承類A
允許多層繼承
class A{}
class B extends A{} // 類B繼承類A,類B是類A的子類
class C extends B{} // 類C繼承類B罢猪,類C是類B的子類近她,同時(shí)也是類A的子類
子類和父類是一種相對(duì)概念
也就是說一個(gè)類是某個(gè)類父類的同時(shí),也可以是另一個(gè)類的子類膳帕。例如上面的這種情況中粘捎,B類是A類的子類,同時(shí)又是C類的父類危彩。
繼承的體系 之 Object類
Object是所有類的父類
如果一個(gè)類沒有顯示定義父類攒磨,那么默認(rèn)繼承Object類
Object類中沒有定義屬性,但是定義了12個(gè)方法恬砂,并且這些方法都是實(shí)例方法咧纠。因此每個(gè)對(duì)象都擁有這14個(gè)方法蓬痒。
繼承過程分析(父子類的實(shí)例話順序)
當(dāng)new子類時(shí)泻骤,父類也new了
執(zhí)行:從父到子
調(diào)用:從子到父
哪個(gè)類定義的屬性,就由哪個(gè)類賦值初始化梧奢,對(duì)于父類來說狱掂,初始化的數(shù)據(jù)是由子類通過super(數(shù)據(jù))傳入給父類的
繼承后子類的成員的變化
了解了繼承給我們帶來的好處,提高了代碼的復(fù)用性亲轨。繼承讓類與類或者說對(duì)象與對(duì)象之間產(chǎn)生了關(guān)系趋惨。那么,當(dāng)繼承出現(xiàn)后惦蚊,類的成員之間產(chǎn)生了那些變化呢器虾?
子類中的成員包括
- 子類自己定義的屬性和方法
- 從父類繼承的屬性和方法
- 子類不能使用從父類繼承的私有成員讯嫂,因?yàn)楸环庋b了。
this和super
繼承中的this(調(diào)用子類成員)
- this可以調(diào)用子類成員
- this可以調(diào)用子類重載的構(gòu)造函數(shù)兆沙,必須是子類構(gòu)造函數(shù)的第一行
繼承中的super(調(diào)用父類成員)
- super調(diào)用父類成員
- super調(diào)用父類構(gòu)造函數(shù)欧芽,必須是子類構(gòu)造函數(shù)的第一行
this和super
this代表自己,this可以調(diào)用
- 自己的屬性和方法
- 在奔類的構(gòu)造函數(shù)內(nèi)調(diào)用其他構(gòu)造函數(shù)
super代表父類葛圃,super可以調(diào)用
- 父類的屬性和方法
- 在子類構(gòu)造中調(diào)用父類構(gòu)造
this和static
this表示實(shí)例
static表示靜態(tài)
static不能調(diào)實(shí)例
對(duì)象名可以調(diào)用static千扔,但是this確不能調(diào)用static
方法重寫(override)
如果子類從父類繼承的方法不能滿足子類的需要,或者不適合子類的需要库正。
此時(shí)子類可以將從父類繼承的方法重寫定義成滿足自己需要的方法曲楚。
重新定義稱為重寫。
class Pet {//寵物
public void sound(){
System.out.println("寵物叫");
}
}
class Dog extends Pet {//狗
//方法重寫
@Override
public void sound() {
System.out.println("汪汪汪");
}
}
class Cat extends Pet{//貓
//方法重寫
@Override
public void sound(){
System.out.println("喵喵喵");
}
}
public class PetShop{//寵物店
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound();//調(diào)用狗的sound()
Cat cat = new Cat();
cat.sound();//調(diào)用貓的sound()
}
}
注意事項(xiàng)
- 在子類中將父類的方法再重新定義一遍
- 方法重寫時(shí)褥符,方法的發(fā)返回值類型龙誊,方法名參數(shù)列表都要與父類一樣。
*子類方法覆蓋父類方法属瓣,必須要保證權(quán)限大于等于父類權(quán)限
class Fu{
void show(){}
public void method(){}
}
class Zi extends Fu{
public void show(){} //擴(kuò)大show的訪問權(quán)限载迄,編譯運(yùn)行沒問題
void method(){} //縮小method的訪問權(quán)限,編譯錯(cuò)誤
}
- 方法重寫時(shí)抡蛙,子類不能縮小父類拋出的異常护昧。
class Pet {//寵物
public void sound() throws RuntimeException{
System.out.println("寵物叫");
}
}
class Cat extends Pet{//貓
//方法重寫
public void sound() throws Exception{//錯(cuò)誤,因?yàn)镋xception 小于 RuntimeException
System.out.println("喵喵喵");
}
}
方法重載與方法重寫的區(qū)別
方法重載:
1.在同一類中(包括從父類繼承的)方法同名不同參與返回值無關(guān)
*方法重寫:
1.方法重寫存在于繼承關(guān)系中
2.父子類之間的方法同名粗截,同參惋耙,同返回
里氏替換原則(父類引用指向子類實(shí)例)
實(shí)現(xiàn):設(shè)計(jì)寵物類,貓類熊昌,狗類绽榛,讓貓和狗繼承寵物類
在寵物類中定義sound方法,表示寵物的叫聲婿屹,但是叫聲不能由具體的行為灭美。
貓和狗重寫父類的sound方法,以實(shí)現(xiàn)具體的叫聲
class Pet {//寵物
public void sound() throws RuntimeException{
}
}
class Dog extends Pet {//狗
//方法重寫
@Override
public void sound() {
System.out.println("汪汪汪");
}
}
class Cat extends Pet{//貓
//方法重寫
@Override
public void sound() throws RuntimeException{
System.out.println("喵喵喵");
}
}
實(shí)現(xiàn):購買寵物要求寵物叫一聲昂利,以此判斷是否買它代碼如下:
public class PetShop{//寵物店
public static void main(String[] args) {
Pet pet= null
pet=new Dog();
pet.sound();
}
}
"運(yùn)行結(jié)果:汪汪汪"
父類引用pet 賦值時(shí) 賦的是子類對(duì)象Dog的實(shí)例届腐。就是這行代碼 pet = new Dog();
我們發(fā)現(xiàn)
pet = new Dog();
這行代碼 = 的右側(cè) new的是子類的實(shí)例,而 = 左側(cè) 是父類的引用蜂奸。我們把這種情況稱為“父類引用指向子類實(shí)例”
"父類引用指向子類對(duì)象的結(jié)論"
父類引用可以代表任何其子類對(duì)象犁苏,代碼表現(xiàn)為
Pet pet = new Dog()
或者-
父類引用指向哪個(gè)子類對(duì)象,調(diào)用的方法就是哪個(gè)子類中的方法扩所。例如:
Pet pet = new Dog(); pet.sound(); //調(diào)用Dog的sound方法 pet = new Cat(); pet.sound(); //調(diào)用Cat的sound方法
父類引用指向子類對(duì)象其實(shí)是增強(qiáng)了父類的功能围详。
注意:
因?yàn)楦割愐谜{(diào)用方法時(shí),必須知道子類有哪些方法祖屏,知道的才能調(diào)用助赞,不知道的是不能調(diào)用的买羞。子類Cat新增的climb()方法父類并不知道,但是父類一定知道子類從父類繼承的方法雹食。所以父類引用只能調(diào)用子類與父類保持繼承關(guān)系的方法哩都。可以是重寫的方法婉徘。
子類引用指向父類實(shí)例:
public static void main(String[] args) {
pet pet = null;
Dog dog = pet;//報(bào)錯(cuò): 子類引用dog指向父類實(shí)例
}
- 當(dāng)子類指向父類引用時(shí)漠嵌,子類會(huì)丟失數(shù)據(jù),因此不允許轉(zhuǎn)換盖呼。
- 如果非要轉(zhuǎn)儒鹿,就要強(qiáng)轉(zhuǎn),認(rèn)可丟失的數(shù)據(jù)几晤。Dog dog = (Dog)pet;
里氏替換原則的調(diào)用規(guī)則:
- 當(dāng)子類指向父類引用時(shí)约炎,子類會(huì)丟失數(shù)據(jù),因此不允許轉(zhuǎn)換蟹瘾。
- 如果非要轉(zhuǎn)圾浅,就要強(qiáng)轉(zhuǎn),認(rèn)可丟失的數(shù)據(jù)憾朴。Dog dog = (Dog)pet;
final關(guān)鍵字
實(shí)現(xiàn):
- 類的屬性不允許修改
- 類的方法不允許覆蓋
- 類不允許繼承