繼承(Inheritance)是面向物件的三大特徵之一,一個類可以繼承另外一個類,被繼承的類叫做父類,繼承其他類的類叫做子類。透過子類繼承父類,子類可以在訪問許可權允許的情況下,獲得父類所有的成員變數和成員方法,子類無需再次重複定義,從而提高程式碼的複用性,方便後期擴充套件。類和類之間產生關係,也是多型(Polymorphism)的基礎。
在Java中,子類繼承父類使用extends關鍵字實現,但是在繼承時要注意滿足is a的關係,例如 Manager is a Employee,Java的類只能夠支援單繼承,不能支援多繼承。即一個子類只能直接或者間接繼承自一個父類。
首先定義一個父類Employee員工類
繼承後成員訪問規則父類中有構造方法、私有化成員和非私有化成員,當子類繼承自父類後有相應的訪問規則
父類的構造方法不能被子類繼承,所以不能直接使用父類的構造方法來建立子類物件。例如之前在父類Employee中定義了帶三個引數的構造方法,但是不能在子類Manager中使用父類Empoyee帶引數的構造方法來建立物件,程式會發生編譯錯誤例如Manger類使用父類的帶引數的構造器建立物件會出現編譯錯誤 //這裡會編譯錯誤,子類不能繼承父類的構造方法 Manager jack=new Manager("jack","TH001",10000.00);
子類可以繼承父類的私有成員,但是不能直接使用,因為使用private關鍵字修飾的成員只能在本類中使用。
例如Employee父類的3個私有化成員變數在子類Manager中不能直接訪問。
/** * 員工姓名 */ private String name; /** * 員工編號 */ private String employeeNo; /** * 員工的薪水 */ private double salary;
子類可以直接繼承和使用父類的非私有成員,但是優先從子類中找,如果找到了就直接使用,如果沒有就繼續去父類中找。Java中所有的類都直接或者間接繼承自java.lang.Object類。例如建立子類Manger物件後可以直接呼叫父類Employee的doWork()方法,但是在子類Manager類中中定義和父類重名的成員變數name,並指定初始值為tom,並且提供get/set方法 /** * 定義和父類重名的成員變數name */ private String name="tom"; @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; }
然後建立Manager物件後獲取該物件的name屬性值,就獲取子類自己的name屬性值
程式執行結果
方法的重寫首先回顧下過載(OverLoad)的概念:過載指的是一個類中定義了多個同名的方法,但是方法的引數不同,即引數的順序或者個數不同。方法重寫(Override)是在父子類中出現了一模一樣的方法時就是方法的重寫,這裡的一模一樣指的是方法的返回值,方法名,形參列表都一樣。
重寫父類Employee的doWork()方法
/** * 子類重寫父類的doWork()方法 */ @Override public void doWork() { System.out.println(this.getName()+"老闆在工作"); }
當建立子類物件呼叫doWork()方法時,此時呼叫的是子類Employee重寫了父類doWork()之後的方法
在使用重寫時需要注意,重寫的方法一定是父子級關係,而且要方法的返回值,名字和方法的引數列表要相同,但是訪問許可權修飾符可以不一樣。Java提供了四種訪問許可權修飾符,分別是public,protected,預設和private,它們都用於修飾類,成員變數和成員方法的訪問許可權,按照許可權大小依次是public> protected>預設>private,其中預設就是什麼都不寫。子類重寫父類的方法時,子類方法的許可權修飾符必須要大於或者等於父類的訪問許可權修飾符,不能比子類的訪問許可權小。如果是重寫的方法可以使用@Override註解標識,該註解僅僅是表示重寫方法標識作用。當父類的方法無法滿足子類的需求時可以重寫父類的方法,如果重寫方法時要繼續呼叫父類的方法來複用父類的邏輯,可以使用super關鍵字呼叫父類的方法,例如super.doWork()。
this和super的三種用法this 表示當前物件的引用地址
this可以訪問本類的成員變數,用於區分成員變數和區域性變數 public void setName(String name) { this.name = name; }
this可以用來訪問本類的成員方法,但是一般都是省略不寫thisthis可以用以來訪問本類的構造方法,無參構造器使用this(),有參構造器使用this(實參列表),在使用this關鍵字訪問構造方法時,2個構造方法不能相互呼叫,而且只能在本來中的構造方法呼叫其他的構造方法,呼叫時放在構造方法的第一行。 /** * 無引數構造器 */ public Employee() { } public Employee(String name) { //呼叫本類的無參構造器 this(); this.name = name; } /** * 有引數構造器 * @param name * @param employeeNo * @param salary */ public Employee(String name, String employeeNo, double salary) { //呼叫本類的有引數構造器 this(name); this.name = name; this.employeeNo = employeeNo; this.salary = salary; }
JDK原始碼中大量使用了this關鍵字來呼叫構造器,例如java.io包的BufferedOutputStream類
super關鍵字表示父類物件的引用,super關鍵字可以用來訪問父類的成員變數,父類的成員方法,父類的構造器。在子類構造方法呼叫父類構造方法需要放在第一行。
訪問父類的成員變數主要是用來區分父子類同名的成員變數,使用方法是super.父類成員變數名訪問父類的成員方法是主要呼叫父類的成員方法,使用方法是super.父類方法名(實參列表); /** * 子類重寫父類的doWork()方法 */ @Override public void doWork() { //訪問父類的成員方法 System.out.println(super.getName()+"老闆在工作"); }
訪問父類的構造方法主要是呼叫父類的構造方法,使用方法是super()或者super(實參列表);
Manager(){ //訪問父類無引數的構造器 super(); } Manager(String name){ //訪問父類帶引數的構造器 super(name); }
Java不支援子類繼承多個父類,但是支援多層(或者叫多級)繼承。例如子類繼承父類,父類繼承爺爺類。super訪問父類的成員變數和成員方法時,優先去父類中找,如果父類中沒有,還會從爺爺類中找,如果找到就使用,如果沒找到就繼續查詢上一級,Java中所有類的頂級父類是java.lang.Object。由於子類的構造方法會預設呼叫父類的無引數的構造方法,如果父類中沒有無引數的構造方法,只定義了有引數的構造方法會編譯錯誤,因為有引數的構造方法會覆蓋Java預設提供的無引數的構造方法,
Manager(String name,double salary ){ //自動呼叫父類的無引數構造器 //super(); }
因此父類必須提供無引數的構造方法
/** * 無引數構造器 */ public Employee() { }
而父類的構造方法可以提供給子類建立物件時對從父類繼承的成員變數進行初始化
Manager(String name,String employeeNo,double salary){ super(name,employeeNo,salary); }
子類呼叫父類構造器的作用
繼承體系的記憶體結構首先定義父類Employee,結構如下所示
呼叫主流程說明
抽象類抽象類和抽象方法的概述和定義抽象類是在class前面使用abstract關鍵字修飾的類,抽象類不能透過new關鍵字建立物件,因此抽象類通常都是用來做父類被子類繼承。抽象類和普通類不同的是除了可以包含成員變數,成員方法和構造方法外還可以定義抽象方法,但是不能建立物件,可以有構造方法為子類繼承父類過來的成員變數初始化。有抽象方法的類一定是抽象類,但是抽象類中可以沒有抽象方法,也就意味著抽象方法必須定義在抽象類中抽象方法是在返回值前使用abstract修飾的方法,但是抽象方法沒有方法體,其他和普通方法一樣。
如果子類是普通類,父類中定義的抽象方法必須在子類中全部重寫。如果子類是抽象類可以不重寫父類的抽象方法。通常在框架設計中會有多層抽象類,多個實現類。例如Java的集合框架其中AbstractCollection和AbstractList都是抽象類,而ArrayList和LinkedList都是AbstractList抽象類的實現類
模板設計模式設計模式是一種解決問題的固定思路和步驟,也就是程式碼設計思路和經驗的總結。Java中常見的設計模式有23種,後續會出專欄結合JDK和框架的原始碼、相關的業務場景以及自己編碼實現23種設計模式
模板設計模式就是在父類中指定一個模板,然後根據具體的情況,在子類中靈活的實現該模板。抽象類體現的就是模板的思想,模板就是將通用的方法在抽象類中具體實現,而模板中不能實現的方法定義成抽象方法,讓使用模板的類去重寫抽象方法實現需求。
模板設計模式的實現步驟
定義抽象父類作為模板在父類中定義模板方法,有實現方法(模板)+抽象方法(填充模板)子類繼承父類,重寫抽象方法(填充父類的模板)測試類:建立子類物件,透過呼叫父類的實現方法加子類重寫後的方法我這裡引入一個電商交易作為業務場景,回想一下你在淘寶、京東、拼多多購買商品時的流程
平臺註冊登入瀏覽商品新增到購物車支付提交訂單因此我這裡定義一個抽象父類AbstractOrderTemplate,抽象類建議命名規範以Abstract開頭
該類中定義了一個實現方法 buy()和6個抽象方法用於實現購買商品的流程,這六個方法都會在buy()方法中被呼叫
然後在測試類中建立各自的子類物件呼叫方法,這裡用到了多型,至於啥是多型,面向物件程式設計進階見分曉。

當建立不同的AbstractOrderTemplate子類物件後呼叫bye()方法,會呼叫各自子類實現父類的那個抽象方法。這樣有個好處,如果某些要實現唯品會下單,只需要新增唯品會的模板即可,原有的程式碼不需要做任何的變更。
final關鍵字final關鍵字表示不可變,可以用來修飾類、變數和成員方法。
class UserInfo{ /** * 註冊來源 * final修飾的成員變數需要在構造器中初始化賦值 */ final String REGISTER_SOURCE; public UserInfo(){ REGISTER_SOURCE="APP"; } public UserInfo(String registerResource){ this.REGISTER_SOURCE=registerResource; }}
static關鍵字
static關鍵字表示靜態的意思,可以用來修飾成員變數,成員方法,程式碼塊。
static關鍵字修飾成員變數,在資料型別前面加static關鍵字,被static修飾的成員變數會變成靜態變數(也稱作類變數),該靜態變數會被該類的所有物件共享一份資料。靜態變數可以透過物件名.變數名和類名.物件名兩種方式訪問。推薦使用類名.靜態變數名訪問,因為這樣不需要建立物件。靜態成員隨著類的載入而被載入,而且只會載入一次。靜態成員會被載入到靜態區,方法區中除了程式碼區以外還有靜態區。定義一個包含靜態成員變數的UserInfo類
class UserInfo{ /** * 國籍 */ static String country;}
在StaticTest類中使用分別建立兩個UserInfo物件,然後使用其中一個UserInfo物件給靜態成員變數賦值,並依次使用兩個UserInfo物件和UserInfo類來訪問靜態成員變數country
程式執行時的記憶體結構靜態成員變數位於靜態區的獨立記憶體空間中,而物件儲存的是靜態成員變數的地址,當透過類或者物件去修改靜態成員變數時,會影響所有使用這個靜態成員變數的類和物件,因為靜態成員變數只會佔據一塊記憶體空間。

由於static修飾的成員變數和成員方法可以直接透過類名訪問,因此在開發工具類時通常會使用static來定義一些全域性變數或者全域性的工具方法,這些全域性變數和方法可以定義在一個類中,並且宣告成static,可以很方便的透過類名訪問。例如JDK提供的陣列工具類java.util.Arrays類中提供的方法都是靜態方法,如果要實現整型陣列的排序,只需要使用Arrays.sort(int[] a,int key)即可
當定義靜態成員變數時,如果有多個地方使用,通常都會使用public static final修飾,如果需求發生變更,只需要修改定義的地方即可,其他呼叫的地方無需更改。