原創公衆号:「bigsai」 除公衆号以外拒絕任意擅自轉載
文章收錄在bigsai公衆号和
回車課堂
課程導學
在Java課堂中,所有老師不得不提到面向對象(Object Oriented),而在談到面向對象的時候,又不得不提到面向對象的三大特征:封裝、繼承、多态。三大特征緊密聯系而又有差別,本課程就帶你學習Java的繼承。
你可能不知道繼承到底有什麼用,但你大機率曾有過這樣的經曆:寫Java項目/作業時候建立很多相似的類,類中也有很多相同的方法,做了很多重複的工作量,感覺很臃腫。而合理使用繼承就能大大減少重複代碼,提高代碼複用性。

繼承的初相識
學習繼承,肯定是先從廣的概念了解繼承是什麼以及其作用,然後才從細的方面學習繼承的具體實作細節,本關就是帶你先快速了解和了解繼承的重要概念。
### 什麼是繼承
繼承(英語:inheritance)是面向對象軟體技術中的一個概念。它使得複用以前的代碼非常容易,能夠大大縮短開發周期,降低開發費用。
Java語言是非常典型的面向對象的語言,在Java語言中繼承就是子類繼承父類的屬性和方法,使得子類對象(執行個體)具有父類的屬性和方法,或子類從父類繼承方法,使得子類具有父類相同的方法。父類有時也叫基類、超類;子類有時也被稱為派生類。
我們來舉個例子:我們知道動物有很多種,是一個比較大的概念。在動物的種類中,我們熟悉的有貓(Cat)、狗(Dog)等動物,它們都有動物的一般特征(比如能夠吃東西,能夠發出聲音),不過又在細節上有差別(不同動物的吃的不同,叫聲不一樣)。在Java語言中實作Cat和Dog等類的時候,就需要繼承Animal這個類。繼承之後Cat、Dog等具體動物類就是子類,Animal類就是父類。
為什麼需要繼承
你可能會疑問為什麼需要繼承?在具體實作的時候,我們建立Dog,Cat等類的時候實作其具體的方法不就可以了嘛,實作這個繼承似乎使得這個類的結構不那麼清晰。
如果僅僅隻有兩三個類,每個類的屬性和方法很有限的情況下确實沒必要實作繼承,但事情并非如此,事實上一個系統中往往有很多個類并且有着很多相似之處,比如貓和狗同屬動物,或者學生和老師同屬人。各個類可能又有很多個相同的屬性和方法,這樣的話如果每個類都重新寫不僅代碼顯得很亂,代碼工作量也很大。
這時繼承的優勢就出來了:可以直接使用父類的屬性和方法,自己也可以有自己新的屬性和方法滿足拓展,父類的方法如果自己有需求更改也可以重寫。這樣使用繼承不僅大大的減少了代碼量,也使得代碼結構更加清晰可見。
是以這樣從代碼的層面上來看我們設計這個完整的Animal類是這樣的:
class Animal
{
public int id;
public String name;
public int age;
public int weight;
public Animal(int id, String name, int age, int weight) {
this.id = id;
this.name = name;
this.age = age;
this.weight = weight;
}
//這裡省略get set方法
public void sayHello()
{
System.out.println("hello");
}
public void eat()
{
System.out.println("I'm eating");
}
public void sing()
{
System.out.println("sing");
}
}
而Dog,Cat,Chicken類可以這樣設計:
class Dog extends Animal//繼承animal
{
public Dog(int id, String name, int age, int weight) {
super(id, name, age, weight);//調用父類構造方法
}
}
class Cat extends Animal{
public Cat(int id, String name, int age, int weight) {
super(id, name, age, weight);//調用父類構造方法
}
}
class Chicken extends Animal{
public Chicken(int id, String name, int age, int weight) {
super(id, name, age, weight);//調用父類構造方法
}
//雞下蛋
public void layEggs()
{
System.out.println("我是老母雞下蛋啦,咯哒咯!咯哒咯!");
}
}
各自的類繼承Animal後可以直接使用Animal類的屬性和方法而不需要重複編寫,各個類如果有自己的方法也可很容易地拓展。上述代碼中你需要注意extends就是用來實作繼承的。
繼承的分類
繼承分為單繼承和多繼承,Java語言隻支援類的單繼承,但可以通過實作接口的方式達到多繼承的目的。我們先用一張表概述一下兩者的差別,然後再展開講解。
定義 | 優缺點 | |
---|---|---|
單繼承 | 一個子類隻擁有一個父類 | 優點:在類層次結構上比較清晰 缺點:結構的豐富度有時不能滿足使用需求 |
多繼承(Java不支援,但可以用其它方式滿足多繼承使用需求) | 一個子類擁有多個直接的父類 | 優點:子類的豐富度很高 缺點:容易造成混亂 |
單繼承,是一個子類隻擁有一個父類,如我們上面講過的Animal類和它的子類。單繼承在類層次結構上比較清晰,但缺點是結構的豐富度有時不能滿足使用需求。
多繼承(Java不支援,但可以實作)
多繼承,是一個子類擁有多個直接的父類。這樣做的好處是子類擁有所有父類的特征,子類的豐富度很高,但是缺點就是容易造成混亂。下圖為一個混亂的例子。
Java雖然不支援多繼承,但是Java有三種實作多繼承效果的方式,分别是内部類、多層繼承和實作接口。
内部類可以繼承一個與外部類無關的類,保證了内部類的獨立性,正是基于這一點,可以達到多繼承的效果。
多層繼承:子類繼承父類,父類如果還繼承其他的類,那麼這就叫多層繼承。這樣子類就會擁有所有被繼承類的屬性和方法。
實作接口無疑是滿足多繼承使用需求的最好方式,一個類可以實作多個接口滿足自己在豐富性和複雜環境的使用需求。類和接口相比,類就是一個實體,有屬性和方法,而接口更傾向于一組方法。舉個例子,就拿鬥羅大陸的唐三來看,他存在的繼承關系可能是這樣的:
如何實作繼承
實作繼承除了上面用到的extends外,還可以用implements這個關鍵字實作。下面,讓我給你逐一講解一下。
extends關鍵字
在Java中,類的繼承是單一繼承,也就是說一個子類隻能擁有一個父類,是以extends隻能繼承一個類。其使用文法為:
class 子類名 extends 父類名{}
例如Dog類繼承Animal類,它是這樣的:
class Animal{} //定義Animal類
class Dog extends Animal{} //Dog類繼承Animal類
子類繼承父類後,就擁有父類的非私有的屬性和方法。如果不明白,請看這個案例,在IDEA下建立一個項目,建立一個test類做測試,分别建立Animal類和Dog類,Animal作為父類寫一個sayHello()方法,Dog類繼承Animal類之後就可以調用sayHello()方法。具體代碼為:
class Animal {
public void sayHello()//父類的方法
{
System.out.println("hello,everybody");
}
}
class Dog extends Animal//繼承animal
{ }
public class test {
public static void main(String[] args) {
Dog dog=new Dog();
dog.sayHello();
}
}
點選運作的時候Dog子類可以直接使用Animal父類的方法。
implements 關鍵字
使用implements 關鍵字可以變相使Java擁有多繼承的特性,使用範圍為類實作接口的情況,一個類可以實作多個接口(接口與接口之間用逗号分開)。Java接口是一系列方法的聲明,一個接口中沒有方法的具體實作 。子類實作接口的時候必須重寫接口中的方法。
我們來看一個案例,建立一個test2類做測試,分别建立doA接口和doB接口,doA接口聲明sayHello()方法,doB接口聲明eat()方法,建立Cat2類實作doA和doB接口,并且在類中需要重寫sayHello()方法和eat()方法。具體代碼為:
interface doA{
void sayHello();
}
interface doB{
void eat();
//以下會報錯 接口中的方法不能具體定義隻能聲明
//public void eat(){System.out.println("eating");}
}
class Cat2 implements doA,doB{
@Override//必須重寫接口内的方法
public void sayHello() {
System.out.println("hello!");
}
@Override
public void eat() {
System.out.println("I'm eating");
}
}
public class test2 {
public static void main(String[] args) {
Cat2 cat=new Cat2();
cat.sayHello();
cat.eat();
}
}
Cat類實作doA和doB接口的時候,需要實作其聲明的方法,點選運作結果如下,這就是一個類實作接口的簡單案例:
繼承的特點
繼承的主要内容就是子類繼承父類,并重寫父類的方法。使用子類的屬性或方法時候,首先要建立一個對象,而對象通過構造方法去建立,在構造方法中我們可能會調用子父類的一些屬性和方法,是以就需要提前掌握this和super關鍵字。建立完這個對象之後,在調用重寫父類的方法,并差別重寫和重載的差別。是以本節根據this、super關鍵字—>構造函數—>方法重寫—>方法重載的順序進行講解。
this和super關鍵字
this和super關鍵字是繼承中非常重要的知識點,分别表示目前對象的引用和父類對象的引用,兩者有很大相似又有一些差別。
this表示目前對象,是指向自己的引用。
this.屬性 // 調用成員變量,要差別成員變量和局部變量
this.() // 調用本類的某個方法
this() // 表示調用本類構造方法
super表示父類對象,是指向父類的引用。
super.屬性 // 表示父類對象中的成員變量
super.方法() // 表示父類對象中定義的方法
super() // 表示調用父類構造方法
此外,this和super關鍵字隻能出現在非static修飾的代碼中。
this()和super()都隻能在構造方法的第一行出現,如果使用this()表示調用目前類的其他構造方法,使用super()表示調用父類的某個構造方法,是以兩者隻能根據自己使用需求選擇其一。
寫一個小案例,建立D1類和子類D2如下:
class D1{
public D1() {}//無參構造
public void sayHello() {
System.out.println("hello");
}
}
class D2 extends D1{
public String name;
public D2(){
super();//調用父類構造方法
this.name="BigSai";//給目前類成員變量指派
}
@Override
public void sayHello() {
System.out.println("hello,我是"+this.name);
}
public void test()
{
super.sayHello();//調用父類方法
this.sayHello();//調用目前類其他方法
}
}
public class test8 {
public static void main(String[] args) {
D2 d2=new D2();
d2.test();
}
}
執行的結果為:
構造方法
構造方法是一種特殊的方法,它是一個與類同名的方法。對象的建立就通過構造方法來完成,其主要的功能是完成對象的初始化。但在繼承中構造方法是一種比較特殊的方法(比如不能繼承),是以要了解和學習在繼承中構造方法的規則和要求。
構造方法可分為有參構造和無參構造,這個可以根據自己的使用需求合理設定構造方法。但繼承中的構造方法有以下幾點需要注意:
父類的構造方法不能被繼承:
因為構造方法文法是與類同名,而繼承則不更改方法名,如果子類繼承父類的構造方法,那明顯與構造方法的文法沖突了。比如Father類的構造方法名為Father(),Son類如果繼承Father類的構造方法Father(),那就和構造方法定義:構造方法與類同名沖突了,是以在子類中不能繼承父類的構造方法,但子類會調用父類的構造方法。
子類的構造過程必須調用其父類的構造方法:
Java虛拟機構造子類對象前會先構造父類對象,父類對象構造完成之後再來構造子類特有的屬性,這被稱為記憶體疊加。而Java虛拟機構造父類對象會執行父類的構造方法,是以子類構造方法必須調用super()即父類的構造方法。就比如一個簡單的繼承案例應該這麼寫:
class A{
public String name;
public A() {//無參構造
}
public A (String name){//有參構造
}
}
class B extends A{
public B() {//無參構造
super();
}
public B(String name) {//有參構造
//super();
super(name);
}
}
如果子類的構造方法中沒有顯示地調用父類構造方法,則系統預設調用父類無參數的構造方法。
你可能有時候在寫繼承的時候子類并沒有使用super()調用,程式依然沒問題,其實這樣是為了節省代碼,系統執行時會自動添加父類的無參構造方式,如果不信的話我們對上面的類稍作修改執行:
方法重寫(Override)
方法重寫也就是子類中出現和父類中一模一樣的方法(包括傳回值類型,方法名,參數清單),它建立在繼承的基礎上。你可以了解為方法的外殼不變,但是核心内容重寫。
在這裡提供一個簡單易懂的方法重寫案例:
class E1{
public void doA(int a){
System.out.println("這是父類的方法");
}
}
class E2 extends E1{
@Override
public void doA(int a) {
System.out.println("我重寫父類方法,這是子類的方法");
}
}
其中@Override注解顯示聲明該方法為注解方法,可以幫你檢查重寫方法的文法正确性,當然如果不加也是可以的,但建議加上。
對于重寫,你需要注意以下幾點:
從重寫的要求上看:
- 重寫的方法和父類的要一緻(包括傳回值類型、方法名、參數清單)
- 方法重寫隻存在于子類和父類之間,同一個類中隻能重載
從通路權限上看:
- 子類方法不能縮小父類方法的通路權限
- 子類方法不能抛出比父類方法更多的異常
- 父類的私有方法不能被子類重寫
從靜态和非靜态上看:
- 父類的靜态方法不能被子類重寫為非靜态方法
- 子類可以定義于父類的靜态方法同名的靜态方法,以便在子類中隐藏父類的靜态方法(滿足重寫限制)
- 父類的非靜态方法不能被子類重寫為靜态方法
從抽象和非抽象來看:
- 父類的抽象方法可以被子類通過兩種途徑重寫(即實作和重寫)
- 父類的非抽象方法可以被重寫為抽象方法
當然,這些規則可能涉及一些修飾符,在第三關中會詳細介紹。
方法重載(Overload)
如果有兩個方法的方法名相同,但參數不一緻,那麼可以說一個方法是另一個方法的重載。方法重載規則如下:
- 被重載的方法必須改變參數清單(參數個數或類型或順序不一樣)
- 被重載的方法可以改變傳回類型
- 被重載的方法可以改變通路修飾符
- 被重載的方法可以聲明新的或更廣的檢查異常
- 方法能夠在同一個類中或者在一個子類中被重載
- 無法以傳回值類型作為重載函數的區分标準
重載可以通常了解為完成同一個事情的方法名相同,但是參數清單不同其他條件也可能不同。一個簡單的方法重載的例子,類E3中的add()方法就是一個重載方法。
class E3{
public int add(int a,int b){
return a+b;
}
public double add(double a,double b) {
return a+b;
}
public int add(int a,int b,int c) {
return a+b+c;
}
}
方法重寫和方法重載的差別:
方法重寫和方法重載名稱上容易混淆,但内容上有很大差別,下面用一個表格列出其中差別:
差別點 | 方法重寫 | 方法重載 |
---|---|---|
結構上 | 垂直結構,是一種父子類之間的關系 | 水準結構,是一種同類之間關系 |
參數清單 | 不可以修改 | 可以修改 |
通路修飾符 | 子類的通路修飾符範圍必須大于等于父類通路修飾符範圍 | |
抛出異常 | 子類方法異常必須是父類方法異常或父類方法異常子異常 |
繼承與修飾符
Java修飾符的作用就是對類或類成員進行修飾或限制,每個修飾符都有自己的作用,而在繼承中可能有些特殊修飾符使得被修飾的屬性或方法不能被繼承,或者繼承需要一些其他的條件,下面就詳細介紹在繼承中一些修飾符的作用和特性。
Java語言提供了很多修飾符,修飾符用來定義類、方法或者變量,通常放在語句的最前端。主要分為以下兩類:
- 非通路修飾符
這裡通路修飾符主要講解public,protected,default,private四種通路控制修飾符。非通路修飾符這裡就介紹static修飾符,final修飾符和abstract修飾符。
public,protected,default(無修飾詞),private修飾符是面向對象中非常重要的知識點,而在繼承中也需要懂得各種修飾符使用規則。
首先我們都知道不同的關鍵字作用域不同,四種關鍵字的作用域如下:
同一個類 | 同一個包 | 不同包子類 | 不同包非子類 | |
---|---|---|---|---|
private | ✅ | |||
default | ||||
protect | ||||
public |
- private:Java語言中對通路權限限制的最窄的修飾符,一般稱之為“私有的”。被其修飾的屬性以及方法隻能被該類的對象通路,其子類不能通路,更不能允許跨包通路。
- default:(也有稱friendly)即不加任何通路修飾符,通常稱為“預設通路權限“或者“包通路權限”。該模式下,隻允許在同一個包中進行通路。
- protected:介于public 和 private 之間的一種通路修飾符,一般稱之為“保護通路權限”。被其修飾的屬性以及方法隻能被類本身的方法及子類通路,即使子類在不同的包中也可以通路。
- public: Java語言中通路限制最寬的修飾符,一般稱之為“公共的”。被其修飾的類、屬性以及方法不僅可以跨類通路,而且允許跨包通路。
Java 子類重寫繼承的方法時,不可以降低方法的通路權限,子類繼承父類的通路修飾符作用域不能比父類小,也就是更加開放,假如父類是protected修飾的,其子類隻能是protected或者public,絕對不能是default(預設的通路範圍)或者private。是以在繼承中需要重寫的方法不能使用private修飾詞修飾。
如果還是不太清楚可以看幾個小案例就很容易搞懂,寫一個A1類中用四種修飾詞實作四個方法,用子類A2繼承A1,重寫A1方法時候你就會發現父類私有方法不能重寫,非私有方法重寫使用的修飾符作用域不能變小(大于等于)。
正确的案例應該為:
class A1 {
private void doA(){ }
void doB(){}//default
protected void doC(){}
public void doD(){}
}
class A2 extends A1{
@Override
public void doB() { }//繼承子類重寫的方法通路修飾符權限可擴大
@Override
protected void doC() { }//繼承子類重寫的方法通路修飾符權限可和父類一緻
@Override
public void doD() { }//不可用protected或者default修飾
}
還要注意的是,繼承當中子類抛出的異常必須是父類抛出的異常或父類抛出異常的子異常。下面的一個案例四種方法測試可以發現子類方法的異常不可大于父類對應方法抛出異常的範圍。
class B1{
public void doA() throws Exception{}
public void doB() throws Exception{}
public void doC() throws IOException{}
public void doD() throws IOException{}
}
class B2 extends B1{
//異常範圍和父類可以一緻
@Override
public void doA() throws Exception { }
//異常範圍可以比父類更小
@Override
public void doB() throws IOException { }
//異常範圍 不可以比父類範圍更大
@Override
public void doC() throws IOException { }//不可抛出Exception等比IOException更大的異常
@Override
public void doD() throws IOException { }
}
通路修飾符用來控制通路權限,而非通路修飾符每個都有各自的作用,下面針對static、final、abstract修飾符進行介紹。
static 修飾符
static 翻譯為“靜态的”,能夠與變量,方法和類一起使用,稱為靜态變量,靜态方法(也稱為類變量、類方法)。如果在一個類中使用static修飾變量或者方法的話,它們可以直接通過類通路,不需要建立一個類的對象來通路成員。
我們在設計類的時候可能會使用靜态方法,有很多工具類比如
Math
,
Arrays
等類裡面就寫了很多靜态方法。static修飾符的規則很多,這裡僅僅介紹和Java繼承相關用法的規則:
- 構造方法不允許聲明為 static 的。
- 靜态方法中不存在目前對象,因而不能使用 this,當然也不能使用 super。
- 靜态方法不能被非靜态方法重寫(覆寫)
- 靜态方法能被靜态方法重寫(覆寫)
可以看以下的案例證明上述規則:
源代碼為:
class C1{
public int a;
public C1(){}
// public static C1(){}// 構造方法不允許被聲明為static
public static void doA() {}
public static void doB() {}
}
class C2 extends C1{
public static void doC()//靜态方法中不存在目前對象,因而不能使用this和super。
{
//System.out.println(super.a);
}
public static void doA(){}//靜态方法能被靜态方法重寫
// public void doB(){}//靜态方法不能被非靜态方法重寫
}
final修飾符
final變量:
- final 表示"最後的、最終的"含義,變量一旦指派後,不能被重新指派。被 final 修飾的執行個體變量必須顯式指定初始值(即不能隻聲明)。final 修飾符通常和 static 修飾符一起使用來建立類常量。
final 方法:
- 父類中的 final 方法可以被子類繼承,但是不能被子類重寫。聲明 final 方法的主要目的是防止該方法的内容被修改。
final類:
- final 類不能被繼承,沒有類能夠繼承 final 類的任何特性。
是以無論是變量、方法還是類被final修飾之後,都有代表最終、最後的意思。内容無法被修改。
abstract 修飾符
abstract 英文名為“抽象的”,主要用來修飾類和方法,稱為抽象類和抽象方法。
抽象方法:有很多不同類的方法是相似的,但是具體内容又不太一樣,是以我們隻能抽取他的聲明,沒有具體的方法體,即抽象方法可以表達概念但無法具體實作。
抽象類:有抽象方法的類必須是抽象類,抽象類可以表達概念但是無法構造實體的類。
抽象類和抽象方法内容和規則比較多。這裡隻提及一些和繼承有關的用法和規則:
- 抽象類也是類,如果一個類繼承于抽象類,就不能繼承于其他的(類或抽象類)
- 子類可以繼承于抽象類,但是一定要實作父類們所有abstract的方法。如果不能完全實作,那麼子類也必須被定義為抽象類
- 隻有實作父類的所有抽象方法,才能是完整類。
比如我們可以這樣設計一個People抽象類以及一個抽象方法,在子類中具體完成:
abstract class People{
public abstract void sayHello();//抽象方法
}
class Chinese extends People{
@Override
public void sayHello() {//實作抽象方法
System.out.println("你好");
}
}
class Japanese extends People{
@Override
public void sayHello() {//實作抽象方法
System.out.println("口你七哇");
}
}
class American extends People{
@Override
public void sayHello() {//實作抽象方法
System.out.println("hello");
}
}
Object類和轉型
提到Java繼承,不得不提及所有類的根類:Object(java.lang.Object)類,如果一個類沒有顯式聲明它的父類(即沒有寫extends xx),那麼預設這個類的父類就是Object類,任何類都可以使用Object類的方法,建立的類也可和Object進行向上、向下轉型,是以Object類是掌握和了解繼承所必須的知識點。而Java向上和向下轉型在Java中運用很多,也是建立在繼承的基礎上,是以Java轉型也是掌握和了解繼承所必須的知識點。
Object類概述
- Object是類層次結構的根類,所有的類都隐式的繼承自Object類。
- Java所有的對象都擁有Object預設方法
- Object類的構造方法有一個,并且是無參構造
Object是java所有類的父類,是整個類繼承結構的頂端,也是最抽象的一個類。像toString()、equals()、hashCode()、wait()、notify()、getClass()等都是Object的方法。你以後可能會經常碰到,但其中遇到更多的就是toString()方法和equals()方法,我們經常需要重寫這兩種方法滿足我們的使用需求。
toString()方法表示傳回該對象的字元串,由于各個對象構造不同是以需要重寫,如果不重寫的話預設傳回
類名@hashCode
格式。
如果重寫toString()方法後直接調用toString()方法就可以傳回我們自定義的該類轉成字元串類型的内容輸出,而不需要每次都手動的拼湊成字元串内容輸出,大大簡化輸出操作。
equals()方法主要比較兩個對象是否相等,因為對象的相等不一定非要嚴格要求兩個對象位址上的相同,有時内容上的相同我們就會認為它相等,比如String 類就重寫了euqals()方法,通過字元串的内容比較是否相等。
向上轉型
向上轉型 : 通過子類對象(小範圍)執行個體化父類對象(大範圍),這種屬于自動轉換。用一張圖就能很好地表示向上轉型的邏輯:
父類引用變量指向子類對象後,隻能使用父類已聲明的方法,但方法如果被重寫會執行子類的方法,如果方法未被重寫那麼将執行父類的方法。
向下轉型
向下轉型 : 通過父類對象(大範圍)執行個體化子類對象(小範圍),在書寫上父類對象需要加括号
()
強制轉換為子類類型。但父類引用變量實際引用必須是子類對象才能成功轉型,這裡也用一張圖就能很好表示向上轉型的邏輯:
子類引用變量指向父類引用變量指向的對象後(一個Son()對象),就完成向下轉型,就可以調用一些子類特有而父類沒有的方法 。
在這裡寫一個向上轉型和向下轉型的案例:
Object object=new Integer(666);//向上轉型
Integer i=(Integer)object;//向下轉型Object->Integer,object的實質還是指向Integer
String str=(String)object;//錯誤的向下轉型,雖然編譯器不會報錯但是運作會報錯
子父類初始化順序
在Java繼承中,父子類初始化先後順序為:
- 父類中靜态成員變量和靜态代碼塊
- 子類中靜态成員變量和靜态代碼塊
- 父類中普通成員變量和代碼塊,父類的構造函數
- 子類中普通成員變量和代碼塊,子類的構造函數
總的來說,就是靜态>非靜态,父類>子類,非構造函數>構造函數。同一類别(例如普通變量和普通代碼塊)成員變量和代碼塊執行從前到後,需要注意邏輯。
這個也不難了解,靜态變量也稱類變量,可以看成一個全局變量,靜态成員變量和靜态代碼塊在類加載的時候就初始化,而非靜态變量和代碼塊在對象建立的時候初始化。是以靜态快于非靜态初始化。
而在建立子類對象的時候需要先建立父類對象,是以父類優先于子類。
而在調用構造函數的時候,是對成員變量進行一些初始化操作,是以普通成員變量和代碼塊優于構造函數執行。
至于更深層次為什麼這個順序,就要更深入了解JVM執行流程啦。下面一個測試代碼為:
class Father{
public Father() {
System.out.println(++b1+"父類構造方法");
}//父類構造方法 第四
static int a1=0;//父類static 第一 注意順序
static {
System.out.println(++a1+"父類static");
}
int b1=a1;//父類成員變量和代碼塊 第三
{
System.out.println(++b1+"父類代碼塊");
}
}
class Son extends Father{
public Son() {
System.out.println(++b2+"子類構造方法");
}//子類構造方法 第六
static {//子類static第二步
System.out.println(++a1+"子類static");
}
int b2=b1;//子類成員變量和代碼塊 第五
{
System.out.println(++b2 + "子類代碼塊");
}
}
public class test9 {
public static void main(String[] args) {
Son son=new Son();
}
}
執行結果:
結語
好啦,本次繼承就介紹到這裡啦,Java面向對象三大特征之一繼承——優秀的你已經掌握。再看看Java面向對象三大特性:封裝、繼承、多态。最後問你能大緻了解它們的特征嘛?
封裝:是對類的封裝,封裝是對類的屬性和方法進行封裝,隻對外暴露方法而不暴露具體使用細節,是以我們一般設計類成員變量時候大多設為私有而通過一些get、set方法去讀寫。
繼承:子類繼承父類,即“子承父業”,子類擁有父類除私有的所有屬性和方法,自己還能在此基礎上拓展自己新的屬性和方法。主要目的是複用代碼。
多态:多态是同一個行為具有多個不同表現形式或形态的能力。即一個父類可能有若幹子類,各子類實作父類方法有多種多樣,調用父類方法時,父類引用變量指向不同子類執行個體而執行不同方法,這就是所謂父類方法是多态的。
最後送你一張圖捋一捋其中的關系吧。
原創不易,bigsai請你幫兩件事幫忙一下:
- 一鍵三連支援一下, 您的肯定是我在平台創作的源源動力。
- 微信搜尋「bigsai」,關注我的公衆号(原創幹貨部落客),不僅免費送你電子書,我還會第一時間在公衆号分享知識技術。加我還可拉你進力扣打卡群一起打卡LeetCode。
- 最近在将csdn的文章整理成電子書,即将第一時間給大家免費放送。