完全掌握Java中的抽象類和介面

2022-06-21 14:00:53
本篇文章給大家帶來了關於的相關知識,其中主要介紹了關於抽象類和介面的相關問題,包括了什麼是抽象類、抽象類再實現多型中的意義等等內容,下面一起來看一下,希望對大家有幫助。

推薦學習:《》

什麼是抽象類

我們之前學過什麼是類,那麼抽象類是不是也是類的一種呢?

聽名字就感覺好抽象呀!說對了,他就是抽象的,不是具體的。在類中沒有包含足夠的資訊來描繪一個具體的物件,這樣的類稱為抽象類。

來看一個抽象類的例子

// 抽象類和抽象方法需要被 abstract 關鍵字修飾
abstract class Shape {
    // 抽象類中的方法一般要求都是抽象方法,抽象方法沒有方法體
    abstract void draw();
}

大家覺得這個抽象類是不是什麼也沒幹,他唯一的方法draw()還是空的。

像這樣的類是不是就沒有包含足夠的資訊來描繪一個具體的物件,自然也就不能範例化物件了。不信你看:

那既然一個類不能範例化,那這種抽象類存在的意義是什麼?

抽象類在實現多型中的意義

抽象類存在的一個最大意義就是被繼承,當被繼承後就可以利用抽象類實現多型。

來看一段程式碼

// 抽象類和抽象方法需要被 abstract 關鍵字修飾
abstract class Shape {
    // 抽象類中的方法一般要求都是抽象方法,抽象方法沒有方法體
    abstract void draw();
}
// 當一個普通類繼承一個抽象類後,這個普通類必須重寫抽象類中的方法
class Cycle extends Shape {
    @Override
    void draw() {  // 重寫抽象類中的draw方法
        System.out.println("畫一個圓圈");
    }
}

public class Test4 {
    public static void main(String[] args) {
        //Shape shape = new Shape();  抽象類雖然不能直接範例化
        // 但可以把一個普通類物件傳給一個抽象類的參照呀,即父類別參照指向子類物件
        Shape shape = new Cycle(); // 這稱作:向上轉型
        
        /*Cycle cycle = new Cycle();
          Shape shape = cycle // 這是向上轉型的另一種寫法
         */
        shape.draw();         // 通過父類別參照呼叫被子類重寫的方法
    }
}

執行之後你就會發現神奇的一幕:

大家在看完了程式碼可能會有很多疑問,別急咱們一個一個的說,

什麼是向上轉型:一句話總結就是「父類別參照指向子類物件」

向上轉型後的變化

  1. 關於方法:父類別參照可以呼叫子類和父類別公用的方法(如果子類重寫了父類別的方法,則呼叫子類的方法),但子類特有的方法無法呼叫。
  2. 關於屬性: 父類別參照可以呼叫父類別的屬性,不可以呼叫子類的屬性

向上轉型的作用

  1. 減少一些重複性的程式碼
  2. 物件範例化的時候可以根據不同需求範例化不同的物件

這樣的話就我們上面的程式碼就可以理解了

看來,我們可以通過子類對抽象類的繼承和重寫,抽象類還真有點用呀!

但這和多型有什麼關係呢,抽象類用起來這麼麻煩,我還不如直接用普通類,也能達到這樣的效果,還不用再寫一個子類呢?

那行,你再看看下面的程式碼,你就知道抽象類在實現多型時的好處了。

abstract class Shape {
    public abstract void draw(); // 抽象方法不能裡有具體的語句
}
// 當一個普通類繼承一個抽象類的時候,再這個子類中必須重寫抽象類中的抽象方法
class Cycle extends Shape {  
    @Override              // 如果不重寫會報錯,但如果繼承的是普通類則不會報錯,用抽象類更安全
    public void draw() {
        System.out.println("畫一個圓圈");
    }
}
class Flower extends Shape { // 不同的子類對父類別的draw方法進行了不同的重寫
    @Override
    public void draw() {
        System.out.println("畫一朵花");
    }
}
class Square extends Shape {
    @Override
    public void draw() {
        System.out.println("畫一個正方形");
    }
}

public class Test4 {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();   // 子類參照cycle
        Flower flower = new Flower(); // 子類參照flower
        Square square = new Square();
        
        // 陣列的型別是Shape,即陣列中每一個元素都是一個父類別參照
        // 在這個過程其實也發生了向上轉型,對抽象類中的方法進行了重寫
        Shape[] shapes = {cycle, flower, square};  // 父類別參照參照不同的子類物件
        for (int i = 0; i < shapes.length; i++) {
            Shape shape = shapes[i]; // 父類別參照shape指向—>當前所對應的子類物件

            shape.draw();  // 通過父類別參照呼叫子類重寫的draw方法
        }

    }
}

呼叫同一個方法竟然列印出了不同的結果,這難道就是所謂的多型

是不是有點懵,下面我們來解釋一下

// 對上面的程式碼補充一下
// 可能你對 Shape[] shapes = {cycle, flower, square};不太理解
// 但上面的程式碼就相當於 

 Shape[] shapes1 = new Shape[3]; // 有三個不同的子類物件呀!陣列大小為3

// (將指向->子類物件)的子類參照賦值給父類別物件,不就相當於該夫類參照指向->所對應的子類物件嗎
//這是向上轉型的另一種寫法,應為前面已經範例化了子類物件  Cycle cycle = new Cycle();   
 shapes1[0] = cycle;  // 如果前面沒範例化子類物件,就要寫成shape1[0] = new Cycle
 shapes1[1] = flower;
 shapes1[2] = square;

對於多型來說,他有這三個要素

  1. 繼承(我們剛才的Cycle類繼承Shape抽象類)
  2. 重寫(我們子類對draw方法的重寫)
  3. 父類別指向子類物件(就是shape1[0] = cycle -->也可以稱作向上轉型)

回頭再看一下我們的程式碼,是不是就剛好符合了多型的三要素。

當我們的父類別參照指向不同的子類物件時,當我們呼叫同一個draw方法時卻輸出了不同的結果。(其實就是該方法再子類中被重寫成了不同形式)這就叫做多型 。

嘻嘻,其實只要只要結合著例子來看,多型也沒那麼難理解呀

那為啥一定要用抽象類呢?我一個普通類繼承普通類來實現多型不可以嗎

當然可以,但不太安全有風險;

但如果是抽象類的話,就不一樣了

從這我們也可以看出,當用抽象類的時候,編譯器自動就對我們是否重寫進行了校驗,而充分利用編譯器的校驗, 在實際開發中是非常有意義的 。所以說抽象類還是有用的

好了,相信到這裡你對抽象類也有了一個大概的認識,下面來簡單做一下總結

  1. 使用abstract修飾的類或方法,就抽象類或者抽象方法
  2. 抽象類是不能具體的描述一個物件,不能用抽象類直接範例化物件
  3. 抽象類裡面的成員變數和成員方法,都是和普通類一樣的,只不過就是不能進行範例化了
  4. 當一個普通類繼承這個抽象類後,那麼這個普通類必須重寫抽象類當中的所有的抽象方法(我們之前說過抽象類是不具體的,沒有包含足夠的資訊來描述一個物件,所以我們需要把他補充完整)
  5. 但當一個抽象類A繼承了抽象類B,這是抽象類A就可以不重寫抽象類B當中的抽象方法
  6. final不能修飾抽象類和抽象方法(因為抽象類存在的最大意義就是被繼承,而被final修飾的不能被繼承,final和抽象,他們兩個是天敵)
  7. 抽象方法不能被private修飾(抽象方法一般都是要被重寫的,你被private修飾了,還怎麼重寫)
  8. 抽象類當中不一定有抽象方法,但如果一個類中有抽象方法,那麼這個類一定是抽象類

介面是什麼

抽象類是從多個類中抽象出來的模板,如果將這種抽象進行的更徹底,則可以提煉出一種更加特殊的「抽象類」——介面(Interface)。

介面是Java中最重要的概念之一,它可以被理解為一種特殊的類,不同的是介面的成員沒有執行體,是由全域性常數和公共的抽象方法所組成。

如何定義一個介面呢?下面我們來看一個栗子

//介面的定義格式與定義類的格式基本相同,將class關鍵字換成 interface 關鍵字,就定義了一個介面

public interface 介面名稱{
// 定義變數
int a = 10;      // 介面當中的成員變數預設都是public static final

// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不寫
void method2();  //  介面當中的成員方法預設都是public abstract, 更推薦用第二種來定義方法 
}

可以看到介面和類其實還是有很多相似點:

介面中也包含抽象方法,所以也不能直接範例化介面,那麼我們怎麼用介面呢?

哈哈,很簡單,我們再用一個普通類實現這個介面不就行了嗎,不同的是抽象類是被子類來繼承而實現的,而介面與類之間則是用關鍵字implements來實現

就像普通類實現實現抽象類一樣,一個類實現某個介面則必須實現該介面中的抽象方法,否則該類必須被定義為抽象類。

通過介面實現多型

鐵汁們!剛才我們是用抽象類來實現多型,那麼現在我們可以嘗試用介面來實現多型,

介面可以看成是一種特殊的類,只能用 interface 關鍵字修飾
interface IShape {
    int a = 10;   介面當中的成員變數預設都是public static final
    int b = 23;
    void draw();  介面當中的成員方法一般只能是抽象方法,預設是public abstract(JDK1.8以前)
  
    default void show() {
        System.out.println("介面中的其他方法");//介面中的其他方法也可以實現,但要用default修飾
    }
    public static void test() {
        System.out.println("這是介面當中的一個靜態的方法");
    }
}

// 一個普通的類要想實現介面,可以用implement, 
//因為介面也是抽象方法的,所以實現介面的這個類也要重寫抽象方法
class Cycle implements IShape {

    @Override
    public void draw() {
        System.out.println("畫一個圓圈");
    }
}
class Square implements IShape {
    @Override
    public void draw() {
        System.out.println("畫一個正方形");
    }
}
class Flower implements IShape {

    @Override
    public void draw() {
        System.out.println("畫一朵花");
    }
}
public class Test4 {
    public static void main(String[] args) {
        // IShape iShape = new IShape(); 介面也不能直接範例化
        Cycle cycle = new Cycle();
        Square square = new Square();
        Flower flower = new Flower();
        // 這裡的IShape介面就相當與抽象類中父類別,介面型別也是一種參照型別

        IShape[] iShapes = {cycle, square, flower}; // 這個過程其實就發生了向上轉型

        for (IShape iShape : iShapes) { // 增強型的for—each迴圈,也可以寫成普通的for迴圈形式
            iShape.draw();              // 通過重寫實現了多型
        }
    }
}
參照變數cycle和square都賦值給了Shape型別的參照變數shape,
但當執行shape.draw()時,java虛擬機器器到底要呼叫誰重寫的的draw方法,
就看此時介面參照的是那個物件的,是shape的、還是cycle的

看一下執行結果

看完程式碼你可能有點暈,但沒關係。一般介面咱也不這麼用,直接使用抽象類不就好了(我只是演示一下用介面也能實現多型)

下面我們來總結一下Java中介面的幾個主要特點

  1. 介面中可以包含變數和方法,變數被隱式指定為 public static final,方法被隱式指定為 public abstract(JDK 1.8 d一個類可以同時實現多個介面,一個類實現某個介面則必須實現該介面中的抽象方法,否則該類必須被定義為抽象類
  2. 介面支援多繼承,即一個介面可以繼承(extends)多個介面,間接解決了 Java 中類不能多繼承的問題。

那麼介面一般用在什麼地方呢?

  • 一般情況下,實現類和它的抽象類之前具有 "is-a" 的關係,但是如果我們想達到同樣的目的,但是又不存在這種關係時,使用介面。
  • 由於 Java 中單繼承的特性,導致一個類只能繼承一個類,但是可以實現一個或多個介面,此時可以使用介面。

下面就讓我們來看看介面的正確用法:幫助java實現「 多繼承 」

由於 Java 中單繼承的特性,導致一個類只能繼承一個類,但是可以實現一個或多個介面,此時可以使用介面。
class Animal {
    String name;        // 不能使用private,後面的子類也要用

    public Animal(String name) { // 父類別的自定義的構造方法
        this.name = name;
    }
}
interface IFlying {   // 自定義多種介面
    void fly();
}
interface IRunning {
    void run();
}
interface ISwimming {
    void swimming();
}
// 小鴨子,不僅會跑,還會游泳、飛行
一個類繼承父類別,並實現多個介面,間接的解決java中不能多繼承的問題
class Duck extends Animal implements IRunning, ISwimming, IFlying {

    public Duck(String name) {  // 子類構造方法
        super(name);            // 必須在子類構造方法的第一行
        // 在給實現子類的構造方法前,先要用super()呼叫實現父類別的構造方法,比較先有父後有子呀!
        // 因為父類別自己定義了構造方法,編譯器不會自動給給子類構造方法中新增super();來實現父類別的構造方法,需要我們自己實現
    }
    // 對介面中的抽象方法進行重寫
    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飛");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用兩條腿跑");
    }

    @Override
    public void swimming() {
        System.out.println(this.name + "正在漂在水上");
    }

}

public class 介面的使用 {  // 不用學我用中文名作為類名,我只是為演示方便
    public static void main(String[] args) {
        Duck duck = new Duck("第一個小鴨子");  // 範例化鴨子物件
        duck.fly();  // 通過參照 變數名.方法名 輸出重寫後的方法
        duck.run();
        duck.swimming();
    }
}
有人可能會說幹嘛用介面,我直接在父類別Animal中實現fly、run、swimming這些屬性,
然後不同的動物子類再繼承父類別這些方法不行嗎?

但問題是,鴨子會fly、swimming,那貓會飛和游泳嗎?你再寫個其他動物的子類是不是就不行了
而用介面呢?我們只是把這種飛、游泳的行為給抽象出來了,

只要一個子類有這種行為,他就可以實現相對應的介面,介面是更加靈活的

上面的程式碼展示了 Java 物件導向程式設計中最常見的用法: 一個類繼承一個父類別, 同時實現多個介面。

繼承表達的含義是 is - a 語意, 而介面表達的含義是 具有 xxx 特性 ,能實現介面的類和該介面並不一定有is_a的關係,只要該類有這個介面的特性就行

貓是一種動物, 具有會跑的特性.

青蛙也是一種動物, 既能跑, 也能游泳

鴨子也是一種動物, 既能跑, 也能遊, 還能飛

這樣設計有什麼好處呢? 時刻牢記多型的好處, 讓程式猿忘記型別. 有了介面之後, 類的使用者就不必關注具體型別,只要這個類有有這個特性就好。

舉個栗子

class Robot implements IRunning {
    private String name;
    public Robot(String name) {
        this.name = name;
    }
    // 對run方法進行重寫
    @Override
    public void run() {
        System.out.println("機器人" + this.name + "正在跑");
    }
}
public class Test4 {
    public static void main(String[] args) {
        Robot robot1 = new Robot("圖圖");
        robot1.run();
    }
}
// 執行結果
機器人圖圖正在跑

只要能跑就行,管他是機器人還是動物呢

推薦學習:《》

以上就是完全掌握Java中的抽象類和介面的詳細內容,更多請關注TW511.COM其它相關文章!