【從基礎學 Java】繼承

和現實世界中:子女能夠繼承父母的一些特徵(如:基因)、財產等同樣。OOP 中也有提供相似的特性,一個類徹底能夠從其它類裏得到一些屬性和方法,而不須要咱們本身從新定義。這種特性簡單但強大 (Simple and powerful)。java

快速瞭解繼承

在 Java 的繼承關係裏:子類能夠從獲取父類的全部的公共和受保護成員(字段、方法和內部類)。固然,構造方法不是成員 (members) ,因此不能被繼承。同時,在 Java 的繼承裏,子類能夠作以下事情:安全

  • 直接使用繼承來的字段
  • 直接使用繼承來的方法
  • 聲明和父類同名字段,隱藏 掉父類字段
  • 經過父類提供的公有/受保護的方法訪問父類的私有成
  • 建立新的字段
  • 重寫父類同方法簽名的實例方法,隱藏 掉父類方法
  • 重寫父類同方法簽名的靜態方法,隱藏 掉父類方法
  • 編寫父類中不存在的方法
  • 使用 super 關鍵字,利用父類構造方法

覆蓋

當子類擁有和父類(或接口)一樣方法簽名的方法,這種現象叫作覆蓋。如如下代碼:ide

class Father
{
    public void doSomthing(){
        // Father do something
    }
}

class Son extends Father{
    @Override
    public void doSomething(){
        // Son do something
    }
}
  • 覆蓋父類實例方法
  • 覆蓋父類靜態方法
  • 覆蓋接口默認方法 (JDK 8+)

對於實例方法的覆蓋,實際是子類擁有本身的方法。
對於靜態方法的覆蓋,要記住:靜態方法時屬於類的,在多態中,調用的始終是類的方法。接口裏的靜態方法永遠不會被覆蓋。優化

Father father = new Son();
    Father.staticMethod(); // 這裏使用的是父類Father的靜態方法

對於接口方法的覆蓋,遵循如下原則:
1.實例方法的優先級大於接口方法 (JDK8+)code

interface Animal{
    default void saySomething(){
        // Animal say something
    }
}
interface Cow extends Animal{
    default void saySomething(){
        // Cow say something
    }
}

class MyCow implements Cow{
    @Override
    public void saySomething(){
        // MyCow say something
    }
    
    Animal myCow = new MyCow();
    myCow.saySomething(); // MyCow say something
}

2.有共同祖先的接口,先被覆蓋的方法(繼承深度低)會被後覆蓋的方法(繼承級別高)覆蓋對象

interface Animal{
    default void saySomething(){
        // Animal say something
    }
}
interface Pig extends Animal{
    default void saySomething(){
        // Pig say something
    }
}
interface BigPig extends Pig{
    default void saySomething(){
        // BigPig say something
    }
}
class MyPig implements Pig, BigPig{
    public static void main(String...args){
        MyPig myPig = new MyPig();
        myPig.saySomething(); // BigPig saySomething()
    }
}

P.S. 若是出現同一繼承級別(上例中 BigPig 和 Pig 都繼承 Animal 接口)或者子類繼承的接口無相關關係,可是接口間有同方法簽名的方法,就會出現覆蓋衝突。須要用 super 關鍵字指明具體實現哪一個接口的方法或者直接覆蓋。繼承

interface Run{
    default void run(){
        // Run run
    }
}

interface Car{
    default void run(){
        // Car run
    }
}

class MyCar implements Run, Car{
    @Override
    public void run(){
        Car.super.run();
    
    }
}

同時,咱們須要注意,Java 子類覆蓋父類方法,應該:接口

  • 方法的訪問權限大於等於父類
  • 方法的返回值小於等於父類
  • 方法拋出的異常小余等於父類

若是子類定義和父類同方法簽名的方法,會有以下結果:ip

x 父類的實例方法 父類的靜態方法
子類的實例方法 覆蓋父類方法 編譯錯誤
子類的靜態方法 編譯錯誤 隱藏父類方法

多態

咱們已經知道,子類能夠覆蓋父類的方法。若是有一個類繼承自另一個類,咱們徹底能夠用一個父類來引用一個子類,如:get

class Person{
    public void saySomething(){
        // Person say something
    }
}
class Father{
    @Override
    public void saySomething(){
        // Father say something
    }
}

class Test{
    pubilc static void main(String...args){
        Father father = new Father();
        Person person = father; // 父類引用子類對象
    }
}

像這種父類引用子類對象的現象,Java 裏叫作多態 (Polymorphism)。在 Java 裏,只有知足以下三個條件,才能叫多態:

  • 繼承關係
  • 覆蓋方法
  • 父類引用子類對象

對於多態方法的運行,編譯器會列舉全部父類和子類符合調用方法簽名(方法名+參數列表)的方法,而後以如下原則編譯、調用方法:

  • 成員變量(編譯和運行都看左邊)
  • 成員方法(編譯看左邊,運行看右邊)
  • 靜態方法(編譯和運行都看左邊)

其中,調用 private, final, static 方法的過程叫靜態綁定,不然叫動態綁定
在使用多態的過程當中,最有效的判斷語句可否經過編譯的方法是:

右邊的類是不是(IS-A)左邊的類

如示例代碼裏的 father(Father) IS-A Person。

阻止繼承

有些狀況下,咱們可能不但願子類覆蓋父類的方法,這時候,用 final 關鍵字修飾方法便可實現該目的。編譯器能夠對用final的方法進行內聯操做優化處理。

強制轉換

在多態裏,咱們知道一個父類能夠引用一個子類對象:

Father father = new Son();

可是,反過來就不行了:

Son son = new Father();

若是須要讓編譯器不報錯,咱們就得進行強制類型轉換操做:

Son son = (Son)new Father();

不過,這麼作有風險,咱們通常要使用 instanceof關鍵字先判斷,可否將父類「安全」轉換成子類:

Father f = ...;
if (f instanceof Son){
    Son son = (Son)f;
}

抽象類與接口

在繼承體系裏,比較經常使用的就是抽象類和接口。它們比較類似:

  • 都能被繼承
  • 都包含抽象方法
  • 都不能被實例化

然而,它們仍是有不一樣的,例如:

  • 抽象類能夠聲明非 final&&static 的字段,接口不行(默認 public static final )
  • 抽象類能夠有構造方法,接口不行
  • 抽象類能夠聲明非 public 方法,接口不行(默認方法是 public )
  • 一個類只能繼承一個抽象類,能夠繼承多個接口

而後,抽象類和接口有不一樣的使用場景:P
抽象類:

  • 在相關類裏共享代碼
  • 規定了一系列通用的方法和屬性
  • 須要定義非靜態、非共有的方法

接口:

  • 定義屬性,如可比較 (Comparable)、可飛(Flyable)
  • 定義行爲,不關心具體實現
  • 但願使用多繼承

繼承的技巧

在 Java 裏,咱們通常按照以下規則使用繼承:

  • 將公共操做放在超類(父類)中
  • 不要使用受保護的域
  • 繼承嚴格遵循 is-a 原則
  • 不要過多得使用反射
相關文章
相關標籤/搜索