Java 繼承
本小節(jié)我們將學(xué)習(xí) Java 的繼承,通過本小節(jié)的學(xué)習(xí),你將知道什么是繼承,繼承有什么特點,如何實現(xiàn)繼承,方法重寫的概念和實現(xiàn),方法重寫和方法重載是比較容易混淆的概念,我們也會介紹兩個概念的區(qū)別,這些都是本小節(jié)的重點,本小節(jié)的最后我們還會介紹 super
關(guān)鍵字以及 final
關(guān)鍵字。
1. 概念和特點
1.1 概念
繼承是面向?qū)ο筌浖夹g(shù)當(dāng)中的一個概念。如果一個類別 B “繼承自” 另一個類別 A,就把這個 B 稱為 “A 的子類”,而把 A 稱為 “B 的父類別” 也可以稱 “A 是 B 的超類”。繼承可以使得子類具有父類別的各種屬性和方法,而不需要再次編寫相同的代碼。
Java 語言提供了類的繼承機制。利用繼承,新建的類可以在原有類的基礎(chǔ)上,使用或者重寫原有類的成員方法,訪問原有類的成員變量。新建的類成為子類,而原有類為新建類的父類,如果 A 是 B 的父類,B 是 C 的父類,那么 C 也是 A 的子類。
1.2 特點
Java 中的繼承為單一繼承,也就是說,一個子類只能擁有一個父類,一個父類可以擁有多個子類。
另外,所有的 Java 類都繼承自 Java.lang.Object
,所以 Object
是所有類的祖先類,除了 Object 外,所有類都必須有一個父類。我們在定義類的時候沒有顯示指定其父類,它默認(rèn)就繼承自 Object
類。
子類一旦繼承父類,就會繼承父類所有開放的特征,不能選擇性地繼承父類特征。
繼承體現(xiàn)的是類與類之間的關(guān)系,這種關(guān)系是 is a
的關(guān)系,也就是說滿足 A is a B
的關(guān)系就可以形成繼承關(guān)系。
下圖展示了 Object
類、父類以及子類的樹形關(guān)系:

緊接著我們會實現(xiàn)一個這樣的樹形關(guān)系。
2. 實現(xiàn)繼承
定義父類 SuperClass
:
// 父類
class SuperClass {
...
}
在 Java 語言中,我們通過 extends
關(guān)鍵字聲明一個類繼承自另一個類:
// 子類
class SubClass extends SuperClass {
...
}
例如,寵物貓和寵物狗都是寵物,都有昵稱、年齡等屬性,都有吃東西、叫喊等行為。我們可以定義一個父類:寵物類。并且寵物貓和寵物狗類都繼承寵物類,繼承樹形圖如下:

代碼實現(xiàn):
public class Pet {
private String name; // 昵稱
private int age; // 年齡
// getter 和 setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 吃東西
public void eat() {
System.out.println(this.getName() + "在吃東西");
}
// 叫喊
public void shout() {
System.out.println("寵物會叫");
}
}
父類寵物類中 name
和 age
都是私有屬性,而對應(yīng)的 getter
、setter
方法,eat
和 shout
方法都是公有方法。
寵物狗類:
public class Dog extends Pet {
// 特有屬性體重
private float weight;
// getter和setter
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
// 特有的方法 run
public void run() {
System.out.println("胖成了" + this.getWeight() + "斤的狗子在奔跑");
}
}
寵物狗類有一個自己特有的屬性 weight
,還有一個特有的方法 run
。
寵物貓類:
public class Cat extends Pet {
public void sleep() {
System.out.println(this.getName() + "睡大覺zzz");
}
}
寵物貓類有一個特有的方法 sleep
,在方法中可以調(diào)用其父類 Pet
的 getName
方法。
調(diào)用類的方法:
// 實例化一個寵物狗
Dog dog = new Dog();
dog.setName("歡歡");
dog.setWeight(30f);
// 調(diào)用繼承自父類的公有方法
dog.eat();
// 調(diào)用其特有方法
dog.run();
// 實例化一個寵物貓
Cat cat = new Cat();
cat.setName("豆豆");
// 調(diào)用繼承自父類的公有方法
cat.eat();
// 調(diào)用其特有方法
cat.sleep();
運行結(jié)果:
歡歡在吃東西
胖成了30.0斤的狗子歡歡在奔跑
豆豆在吃東西
豆豆睡大覺zzz
3. 方法重寫
3.1 概念
如果一個類從它的父類繼承了一個方法,如果這個方法沒有被標(biāo)記為 final
或 static
,就可以對這個方法進(jìn)行重寫。重寫的好處是:能夠定義特定于子類類型的行為,這意味著子類能夠基于要求來實現(xiàn)父類的方法。
3.2 實例
在上述父類 Pet
中有一個 shout
方法,我們知道小貓和小狗的叫聲是不同的,此時可以使用方法重寫,在 Dog
類和 Cat
類中重寫 shout
方法。
Dog 類:
class Dog extends Pet{
// 重寫 shout 方法
public void shout() {
System.out.println(this.getName() + "汪汪汪地叫~");
}
}
Cat 類:
class Cat extends Pet{
@Override // 使用注解
public void shout() {
System.out.println(this.getName() + "喵喵喵地叫~");
}
}
Tips:在要重寫的方法上面,可以選擇使用
@Override
注解,讓編譯器幫助檢查是否進(jìn)行了正確的重寫。如果重寫有誤,編譯器會提示錯誤。雖然這個注解不是必須的,但建議日常編碼中,在所有要重寫的方法上都加上@Override
注解,這樣可以避免我們由于馬虎造成的錯誤。
可以使用對象實例調(diào)用其重寫的方法:
// 實例化一個寵物狗
Dog dog = new Dog();
dog.setName("歡歡");
// 調(diào)用重寫方法
dog.shout();
// 實例化一個寵物貓
Cat cat = new Cat();
cat.setName("豆豆");
// 調(diào)用重寫方法
cat.shout();
運行結(jié)果:
歡歡汪汪汪地叫~
豆豆喵喵喵地叫~
3.3 方法重寫規(guī)則
關(guān)于方法重寫,有以下規(guī)則:
- 重寫方法的參數(shù)列表應(yīng)該與原方法完全相同;
- 返回值類型應(yīng)該和原方法的返回值類型一樣或者是它在父類定義時的子類型;
- 重寫方法訪問級別限制不能比原方法高。例如:如果父類方法聲明為公有的,那么子類中的重寫方法不能是私有的或是保護的。具體限制級別參考訪問修飾符;
- 只有被子類繼承時,方法才能被重寫;
- 方法定義為
final
,將不能被重寫(final
關(guān)鍵字將在本節(jié)后面講到); - 一個方法被定義為 static,將使其不能被重寫,但是可以重新聲明;
- 一個方法不能被繼承,那么也不能被重寫;
- 和父類在一個包中的子類能夠重寫任何沒有被聲明為 private 和 final 的父類方法;
- 和父類不在同一個包中的子類只能重寫 non-final 方法或被聲明為 public 或 protected 的方法;
- 一個重寫方法能夠拋出任何運行時異常,不管被重寫方法是否拋出異常。然而重寫方法不應(yīng)該拋出比被重寫方法聲明的更新更廣泛的已檢查異常。重寫方法能夠拋出比被重寫方法更窄或更少的異常;
- 構(gòu)造方法不能重寫。
3.4 方法重寫和方法重載的區(qū)別
Java 中的方法重寫(Overriding
)是說子類重新定義了父類的方法。方法重寫必須有相同的方法名,參數(shù)列表和返回類型。覆蓋者訪問修飾符的限定大于等于父類方法。
而方法重載(Overloading
)發(fā)生在同一個類里面兩個或者是多個方法的方法名相同但是參數(shù)不同的情況。
4. 訪問修飾符
4.1 作用
在上一小節(jié)封裝的實現(xiàn)中,我們使用 private
和 public
兩種訪問修飾符實現(xiàn)了對類的封裝?,F(xiàn)在終于到了詳細(xì)了解訪問修飾符的時候了。
為了實現(xiàn)對類的封裝和繼承,Java 提供了訪問控制機制。通過訪問控制機制,類的設(shè)計者可以掩蓋變量和方法來達(dá)到維護類自身狀態(tài)的目的,而且還可以將另外一些需要暴露的變量和方法提供給別的類進(jìn)行訪問和修改。
4.2 種類
Java 一共提供了 4 種訪問修飾符:
- private:私有的,只允許在本類中訪問;
- protected:受保護的,允許在同一個類、同一個包以及不同包的子類中訪問;
- 默認(rèn)的:允許在同一個類,同一個包中訪問;
- public:公共的,可以再任何地方訪問。
下表按照限定能力從大到小列出了訪問修飾符在不同作用域的作用范圍:
訪問控制修飾符 | 同一個類 | 同一個包 | 不同包的子類 | 不同包的非子類 |
---|---|---|---|---|
private(私有的) | ? | ? | ? | ? |
default(默認(rèn)的) | ? | ? | ? | ? |
protected(受保護的) | ? | ? | ? | ? |
public(公共的) | ? | ? | ? | ? |
5. super 關(guān)鍵字
super
是用在子類中的,目的是訪問直接父類的變量或方法。注意:
- super 關(guān)鍵字只能調(diào)用父類的
public
以及protected
成員; - super 關(guān)鍵字可以用在子類構(gòu)造方法中調(diào)用父類構(gòu)造方法;
- super 關(guān)鍵字不能用于靜態(tài) (
static
) 方法中。
5.1 調(diào)用父類構(gòu)造方法
父類的構(gòu)造方法既不能被繼承,也不能被重寫。
可以使用 super
關(guān)鍵字,在子類構(gòu)造方法中要調(diào)用父類的構(gòu)造方法,語法為:
super(參數(shù)列表)
例如,父類 Pet
中存在構(gòu)造方法:
public Pet(String name) {
System.out.println("寵物實例被創(chuàng)建了,寵物名字為" + name);
}
子類 Dog
的構(gòu)造方法中調(diào)用父類構(gòu)造方法:
public Dog(String name) {
super(name);
System.out.println("小狗實例被創(chuàng)建了");
}
調(diào)用 Dog
有參構(gòu)造方法,進(jìn)行實例化:
new Dog("花花");
運行結(jié)果:
寵物實例被創(chuàng)建了,寵物名字為花花
小狗實例被創(chuàng)建了
5.2 調(diào)用父類屬性
子類中可以引用父類的成員變量,語法為:
super.成員變量名
例如,在 Dog 類中調(diào)用父類的成員變量 birthday
:
class Pet {
protected String birthday;
}
class Dog extends Pet {
public Dog() {
System.out.println("寵物生日:" + super.birthday);
}
}
5.3 調(diào)用父類方法
有時候我們不想完全重寫父類方法,可以使用 super
關(guān)鍵字調(diào)用父類方法,調(diào)用父類方法的語法為:
super.方法名(參數(shù)列表)
例如,Cat 類調(diào)用父類 Pet 的 eat 方法:
class Pet {
public void eat() {
System.out.println("寵物吃東西");
}
}
class Cat extends Pet{
public void eat() {
// 在 eat 方法中調(diào)用父類 eat 方法
super.eat();
System.out.println("小貓飯量很小");
}
}
class Test {
public static void main(String[] args) {
Cat cat = new Cat();
cat.eat();
}
}
運行結(jié)果:
寵物吃東西
小貓飯量很小
5.4 super 與 this 的對比
this
關(guān)鍵字指向當(dāng)前類對象的引用,它的使用場景為:
- 訪問當(dāng)前類的成員屬性和成員方法;
- 訪問當(dāng)前類的構(gòu)造方法;
- 不能在靜態(tài)方法中使用。
super
關(guān)鍵字指向父類對象的引用,它的使用場景為:
- 訪問父類的成員屬性和成員方法;
- 訪問父類的構(gòu)造方法;
- 不能在靜態(tài)方法中使用。
另外,需要注意的是,在構(gòu)造方法調(diào)用時,super 和 this 關(guān)鍵字不能同時出現(xiàn)。
6. final 關(guān)鍵字
final
關(guān)鍵字可以作用于類、方法或變量,分別具有不同的含義。在使用時,必須將其放在變量類型或者方法返回之前,建議將其放在訪問修飾符和 static
關(guān)鍵字之后,例如:
// 定義一個常量
public static final int MAX_NUM = 50;
6.1 final 作用于類
當(dāng) final
關(guān)鍵字用于類上面時,這個類不會被其他類繼承:
final class FinalClass {
public String name;
}
// final類不能被繼承,編譯會報錯
public class SubClass extends FinalClass {
}
編譯執(zhí)行,將會報錯:
SubClass.java:1: 錯誤: 無法從最終FinalClass進(jìn)行繼承
public class SubClass extends FinalClass {
^
1 個錯誤
6.2 final 作用于方法
當(dāng)父類中方法不希望被重寫時,可以將該方法標(biāo)記為 final
:
class SuperClass {
public final void finalMethod() {
System.out.println("我是final方法");
}
}
class SubClass extneds SuperClass {
// 被父類標(biāo)記為final的方法不允許被繼承,編譯會報錯
@Override
public void finalMethod() {
}
}
編輯執(zhí)行,將會報錯:
SubClass.java:4: 錯誤: SubClass中的finalMethod()無法覆蓋SuperClass中的finalMethod()
public void finalMethod() {
^
被覆蓋的方法為final
1 個錯誤
6.3 final 作用于變量
對于實例變量,可以使用 final 修飾,其修飾的變量在初始化后就不能修改:
class Cat {
public final String name = "小花";
}
實例化 Cat 類,重新對 name
字段賦值:
Cat cat = new Cat();
cat.name = "小白";
編譯執(zhí)行,將會報錯:
Cat.java:7: 錯誤: 無法為最終變量name分配值
cat.name = "小白";
^
1 個錯誤
7. 小結(jié)
本小節(jié)我們學(xué)習(xí)了 Java 類的繼承,通過類的繼承,可以大大增加代碼的復(fù)用性。Java 是單繼承的語言,所有類的根類都是 Object
,繼承通過 extends
關(guān)鍵字實現(xiàn)。要注意方法重寫和方法重載的區(qū)別,不要混淆。類方法和 final
方法不能被重寫。通過 super
關(guān)鍵字可以訪問父類對象成員。final
關(guān)鍵字可以作用于類、方法和變量。