JAVA筆記(九)面向對象——繼承
引言:java的核心思想是面向對象,我們會將實際中客觀存在的所有東西看做對象,進一步對對象的共同屬性和方法抽取形成類,那么如果抽取出來的多個類有著相同的屬性和方法,是否可以進一步抽取呢?就需要我們今天的繼承知識點了。
一、JAVA繼承:
1.繼承的概念:
繼承是java面向對象編程技術的一塊基石,因為它允許創建分等級層次的類。
繼承就是子類繼承父類的特征和行為,使得子類對象(實例)具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行為。
我們用實際生活中的例子解釋:


兔子和羊屬于食草動物類,獅子和豹屬于食肉動物類。
食草動物和食肉動物又是屬于動物類。
所以繼承需要符合的關系是:is-a,父類更通用,子類更具體。
雖然食草動物和食肉動物都是屬于動物,但是兩者的屬性和行為上有差別,所以子類會具有父類的一般特性也會具有自身的特性。
2.繼承的語法:
通過 extends 這個關鍵字來實現繼承,而且所有的類都是繼承于 java.lang.Object,當一個類沒有繼承的關鍵字,則默認繼承object(這個類在 java.lang 包中,所以不需要 import)祖先類。
子類 extends 父類{
//屬性和方法省略
}
如我們用上述例子來學習繼承:
父類:
package cn.hz;
/**
* @author hz
* @version 1.0
*
* 父類:動物類
*/
public class Animal {
//屬性和方法省略...
}
子類:(以食草動物為例,其他省略)
package cn.hz;
/**
* @author hz
* @version 1.0
*
* 子類:食草動物,繼承于動物父類
*/
public class Herbivore extends Animal {
//屬性和方法省略...
}
3.繼承的使用:
當一個類A繼承類B以后,可以直接通過A類創建該類對象,也可以通過父類的引用指向子類
子類A extends 父類B{...}
創建子類A對象方式一:
子類A 子類對象=new 子類A();
例如上述例子,創建食草動物a:
Herbivore a=new Herivore();
創建子類A對象方式二:
父B 子類對象=new 子類A();
例如上述例子,創建食草動物a:
Animal a=new Herivore();
4.繼承的意義:
看完繼承的概念和語法,可能有很多同學會有一個疑問?那我為什么要用繼承呢?那我們就通過一個例子來具體看繼承使用的好處。
還是以上述例子進行實現,開發動物類,其中動物分別為狗和企鵝,需求如下:
狗:屬性(昵稱,健康值,親密度,品種),方法(打印信息,獲取設置相關屬性方法,構造方法)
企鵝:屬性(昵稱,健康值,親密度,性別),方法(打印信息,獲取設置相關屬性方法,構造方法)
類圖如下:


代碼如下:
package cn.hz;
/**
* @author hz
* @version 1.0
*
* 狗的類
*/
public class Dog {
private String name; //昵稱
private Integer health; //健康值
private Integer love; //親密度
private String strain; //品種
public Dog() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHealth() {
return health;
}
public void setHealth(Integer health) {
this.health = health;
}
public Integer getLove() {
return love;
}
public void setLove(Integer love) {
this.love = love;
}
public String getStrain() {
return strain;
}
public void setStrain(String strain) {
this.strain = strain;
}
public void print(){
System.out.println("狗的基本信息:...省略");
}
}
package cn.hz;
/**
* @author hz
* @version 1.0
*/
public class Penguin {
private String name; //昵稱
private Integer health; //健康值
private Integer love; //親密度
private String sex; //性別
public Penguin() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHealth() {
return health;
}
public void setHealth(Integer health) {
this.health = health;
}
public Integer getLove() {
return love;
}
public void setLove(Integer love) {
this.love = love;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void print(){
System.out.println("企鵝信息:...省略");
}
}
通過對類圖和代碼進行比較分析,我們發現出現了上述寫法中存在兩個問題:
- 代碼存在大量冗余,狗和企鵝有著共同的屬性name,health,love和打印方法,重復代碼過多;
- 代碼擴展性不高,如果現在需要添加一個新的動物貓,需要將所有屬性全部重新編寫;
我們使用繼承將上述代碼進行優化
類圖如下:


代碼如下:
package cn.hz;
/**
* @author hz
* @version 1.0
*
* 父類:動物類
*/
public class Animal {
private String name; //昵稱
private Integer health; //健康值
private Integer love; //親密度
public Animal() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getHealth() {
return health;
}
public void setHealth(Integer health) {
this.health = health;
}
public Integer getLove() {
return love;
}
public void setLove(Integer love) {
this.love = love;
}
public void print(){
System.out.println("動物信息:...省略");
}
}
package cn.hz;
/**
* @author hz
* @version 1.0
*
* 狗的類
*/
public class Dog {
private String strain; //品種
public String getStrain() {
return strain;
}
public void setStrain(String strain) {
this.strain = strain;
}
}
package cn.hz;
/**
* @author hz
* @version 1.0
*/
public class Penguin {
private String sex; //性別
public void setSex(String sex) {
this.sex = sex;
}
}
經過優化,我們將狗和企鵝共同的屬性進行抽取放入到父類,子類自己只編寫自己特有的屬性和方即可,這樣代碼的冗余問題得到解決,代碼的擴展性也得到提高。
5.java繼承方式:
- Java 不支持多繼承,只支持單繼承
- java支持多重繼承,可以實現繼承鏈,如上述食草動物繼承動物,兔子繼承食草動物


6.繼承特性:
- 子類擁有父類非 private 的屬性、方法。
- 子類可以擁有自己的屬性和方法,即子類可以對父類進行擴展。
- 子類可以用自己的方式實現父類的方法。
- Java 的繼承是單繼承,但是可以多重繼承,單繼承就是一個子類只能繼承一個父類,多重繼承就是,例如 B 類繼承 A 類,C 類繼承 B 類,所以按照關系就是 B 類是 C 類的父類,A 類是 B 類的父類,這是 Java 繼承區別于 C++ 繼承的一個特性。
- 提高了類之間的耦合性(繼承的缺點,耦合度高就會造成代碼之間的聯系越緊密,代碼獨立性越差)。
二、繼承綜合案例:
由于面向對象階段,概念性的東西很抽象,很多同學反饋學習的時候很明白,但是實際使用時就很蒙,所有本章節我們特意通過案例分析加深大家對繼承的理解:
代碼如下:B類繼承A類,創建B類的兩個對象,最后執行結果是什么?為什么?
package cn.hz;
/**
* @author hz
* @version 1.0
*/
public class A {
public A(){
System.out.println("A的構造方法");
}
static{
System.out.println("A的靜態塊");
}
{
System.out.println("A的動態塊");
}
}
package cn.hz;
/**
* @author hz
* @version 1.0
*/
public class B extends A {
public B(){
System.out.println("B的構造方法");
}
static{
System.out.println("B的靜態塊");
}
{
System.out.println("B的動態塊");
}
}
package cn.hz;
/**
* @author hz
* @version 1.0
*/
public class TestAB {
public static void main(String[] args) {
//創建對象
B b=new B();
B b1=new B();
}
}
分析:
上述代碼B繼承A,最后創建了兩個B的對象,首先我們知道一個類創建對象需要先加載類,所有對于一個類含有靜態塊(對類的初始化),動態塊(對象創建初始化),構造方法(對使用該構造方法創建對象初始化),他們的執行順序是:
靜態塊–動態塊-構造方法
而如果兩個類存在繼承關系,子類在創建對象時一定會調用父類對應的構造方法,而創建對象使用構造方法之前一定會先進行類的加載,所有父類靜態塊也會執行調用,但是一定注意創建對象時是父類動態塊完成初始化然后執行構造方法以后,再通過子類動態塊完成子類初始化再通過構造方法創建對象,即:
父類靜態塊-子類靜態塊–父類動態塊-父類構造方法-子類動態塊-子類構造方法
而結合代碼我們發現創建了兩個對象,這個時候需要注意了,當類加載完成以后,靜態塊完成初始化后期便不會再執行,但是動態塊和構造方法是每創建一個對象都會執行一次,所有最終執行結果如下:


小結:
該案例重點是考察大家對于對象創建和繼承的理解,大家可以就該案例進行變形演示進行總結分析。
三、方法重寫:
通過繼承我們可以解決代碼冗余性問題,提高代碼的可擴展性,但是我們在實際開發中發現在繼承中父類定義的方法子類并不適用,而需要重寫?那么什么是重寫呢?重寫時需要注意什么呢?
1.重寫的定義:
重寫是子類對父類的允許訪問的方法的實現過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!
重寫的好處在于子類可以根據需要,定義特定于自己的行為。 也就是說子類能夠根據需要實現父類的方法。
重寫方法不能拋出新的檢查異常或者比被重寫方法申明更加寬泛的異常。例如: 父類的一個方法申明了一個檢查異常 IOException,但是在重寫這個方法的時候不能拋出 Exception 異常,因為 Exception 是 IOException 的父類,只能拋出 IOException 的子類異常。
2.重寫的具體使用:
如上述例子,動物的類中定義一個打印方法,但是不同子類打印內容不同,所以需要對其進行重寫,代碼如下:
public class Animal{
//屬性省略...
public void print(){
//代碼省略...
}
}
public class Dog extends Aniaml{
//屬性省略
@Override
public void print() {
//子類具體實現
}
}
通過上述代碼我們發現幾個問題:
- 父類的打印方法的方法實現體沒有任何意義,是否可以省略?–(具體見抽象類抽象方法章節詳細講解)
- 子類重寫父類的同名方法,有什么要求?–(見后面重寫規則)
3.重寫規則:
- 參數列表與被重寫方法的參數列表必須完全相同。
- 返回類型與被重寫方法的返回類型可以不相同,但是必須是父類返回值的派生類(java5 及更早版本返回類型要一樣,java7 及更高版本可以不同)。
- 訪問權限不能比父類中被重寫的方法的訪問權限更低。例如:如果父類的一個方法被聲明為 public,那么在子類中重寫該方法就不能聲明為 protected。
- 父類的成員方法只能被它的子類重寫。
- 聲明為 final 的方法不能被重寫。
- 聲明為 static 的方法不能被重寫,但是能夠被再次聲明。
- 子類和父類在同一個包中,那么子類可以重寫父類所有方法,除了聲明為 private 和 final 的方法。
- 子類和父類不在同一個包中,那么子類只能夠重寫父類的聲明為 public 和 protected 的非 final 方法。
- 重寫的方法能夠拋出任何非強制異常,無論被重寫的方法是否拋出異常。但是,重寫的方法不能拋出新的強制性異常,或者比被重寫方法聲明得更廣泛的強制性異常,反之則可以。
- 構造方法不能被重寫。
- 如果不能繼承一個類,則不能重寫該類的方法。
四、equals方法重寫:
1.為什么重寫equals方法:
上述我們具體的講解了方法的重寫,我們來看實際生活中的一個例子:
定義一個學生類Student,屬性身份證號id,姓名name;如果兩個人的身份證號和姓名相同,實際生活中則這兩個人為同一個人,比較的話結果應該為true;那么我們來看看如果在java中使用代碼實現結果會如何?
package cn.hz;
/**
* @author hz
* @version 1.0
* 定義學生類
*/
public class Student {
private Integer id; //屬性:身份證號
private String name; //屬性:姓名
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package cn.hz;
/**
* @author hz
* @version 1.0
*/
public class StudentTest {
public static void main(String[] args) {
//創建學生對象1
Student student1=new Student();
student1.setId(123456);
student1.setName("張三");
//創建學生對象2
Student student2=new Student();
student2.setId(123456);
student2.setName("張三");
System.out.println("兩個學生對象是否為同一個:"+student1.equals(student2));
}
}
通過運行我們得出結果居然是false:


很明顯這樣的結果并不符合我們實際,java中結果為什么會是這樣呢?通過分析我們知道java所有類都繼承于 Object父類,而我們使用的equals比較方法其實也是Object類的,通過API查找底層代碼我們得知Object中equals源碼如下:


本質上其實就是使用的==,==比較的是兩個對象在堆中創建的地址,結果當然為false,那么我們如何讓我們Student類中的比較方法滿足我們實際需求呢?這就需要我們重新父類Object中的equals方法。
2.重寫equals方法的具體實現:
通過分析我們可以得知實際比較的幾種情況:
- 如果兩個對象在堆內層地址一樣,則肯定相等
- 如果兩個對象的類型不相同,則肯定不相等
- 如果地址不同,類型相同,兩個對象的所有屬性都相同,則兩個對象相等
代碼實現如下:
package cn.hz;
/**
* @author hz
* @version 1.0
* 定義學生類
*/
public class Student {
private Integer id; //屬性:身份證號
private String name; //屬性:姓名
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 重寫equals方法
* @param obj
* @return
*/
@Override
public boolean equals(Object obj) {
//如果兩個對象地址相同,則兩個對象對象內容一定相同;
if(this==obj){
return true;
}
//如果兩個對象的類型不同,則兩個對象內容肯定不同,
//instanceof表示判定左側對象是否是右側類型,返回值為boolean
if(!(obj instanceof Student)){
return false;
}
//參數為object類型,已經判定類型,則可以確定obj為student類型,為了獲取屬性,obj轉換為student
Student o=(Student) obj;
//如果兩個對象的地址不同,類型相同,如果兩個對象的屬性值一樣則為同一個對象,結果true
if(this.id==o.id && this.name.equals(o.name) ){
return true;
}else{
return false;
}
}
}
package cn.hz;
/**
* @author hz
* @version 1.0
*/
public class StudentTest {
public static void main(String[] args) {
//創建學生對象1
Student student1=new Student();
student1.setId(123456);
student1.setName("張三");
//創建學生對象2
Student student2=new Student();
student2.setId(123456);
student2.setName("張三");
System.out.println("兩個學生對象是否為同一個:"+student1.equals(student2));
}
}
執行結果為true,如下:


小結:
在實際生活中很多時候需要我們進行對象比較,equals方法主要用于比較內容,==用于比較地址,為了讓equals符合我們的實際需要,很多時候我們需要對equals方法進行重寫。
版權聲明:本文內容由互聯網用戶自發貢獻,該文觀點僅代表作者本人。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如發現本站有涉嫌抄襲侵權/違法違規的內容, 請發送郵件至 舉報,一經查實,本站將立刻刪除。