CodeGym /Java Blog /Toto sisi /Java 多態性
John Squirrels
等級 41
San Francisco

Java 多態性

在 Toto sisi 群組發布
OOP 相關問題是 IT 公司 Java 開發人員職位技術面試的一個組成部分。在本文中,我們將討論 OOP 的一個原則——多態性。我們將重點關注面試中經常被問到的方面,並舉幾個例子來說明問題。

Java 中的多態性是什麼?

多態性是程序以相同方式處理具有相同接口的對象的能力,而無需有關對象特定類型的信息。如果您回答有關什麼是多態性的問題,您很可能會被要求解釋您的意思。在不引發一堆額外問題的情況下,再次向面試官提出​​所有問題。 面試時間:Java中的多態性——1您可以從 OOP 方法涉及基於對象之間的交互構建 Java 程序這一事實開始,這些對象基於類。類是以前編寫的藍圖(模板),用於在程序中創建對象。此外,一個類總是有一個特定的類型,在良好的編程風格下,它有一個表明其用途的名稱。此外,可以注意到,由於 Java 是強類型的,因此在聲明變量時程序代碼必須始終指定對像類型。除此之外,嚴格類型可以提高代碼的安全性和可靠性,並且即使在編譯時也可以防止由於類型不兼容而導致的錯誤(例如,嘗試將字符串除以數字)。自然地,編譯器必須“知道” 聲明的類型——它可以是來自 JDK 的類,也可以是我們自己創建的類。向面試官指出,我們的代碼不僅可以使用聲明中指定類型的對象,還可以使用其後代。這一點很重要:我們可以將許多不同的類型作為一個類型來處理(前提是這些類型是從基類型派生的)。這也意味著如果我們聲明一個類型為超類的變量,那麼我們可以將其後代之一的實例分配給該變量。如果你舉個例子,面試官會喜歡的。選擇一些可以由多個類(的基類)共享的類,並讓其中的幾個繼承它。基類:

public class Dancer {
    private String name;
    private int age;

    public Dancer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void dance() {
        System.out.println(toString() + " I dance like everyone else.");
    }

    @Override
    public String toString() {
        Return "I'm " + name + ". I'm " + age + " years old.";
    }
}
在子類中,覆蓋基類的方法:

public class ElectricBoogieDancer extends Dancer {
    public ElectricBoogieDancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString () + " I dance the electric boogie!");
    }
}

public class Breakdancer extends Dancer {

    public Breakdancer(String name, int age) {
        super(name, age);
    }
// Override the method of the base class
    @Override
    public void dance() {
        System.out.println(toString() + " I breakdance!");
    }
}
多態性的示例以及如何在程序中使用這些對象:

public class Main {

    public static void main(String[] args) {
        Dancer dancer = new Dancer("Fred", 18);

        Dancer breakdancer = new Breakdancer("Jay", 19); // Widening conversion to the base type 
        Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20); // Widening conversion to the base type

        List<dancer> disco = Arrays.asList(dancer, breakdancer, electricBoogieDancer);
        for (Dancer d : disco) {
            d.dance(); // Call the polymorphic method
        }
    }
}
主要方法中,顯示行

Dancer breakdancer = new Breakdancer("Jay", 19);
Dancer electricBoogieDancer = new ElectricBoogieDancer("Marcia", 20);
聲明超類的變量並為其分配一個對象,該對像是其後代之一的實例。您很可能會被問到,為什麼編譯器不會因為賦值運算符左右兩側聲明的類型不一致而大吃一驚——畢竟,Java 是強類型的。解釋擴大類型轉換在這裡起作用——對對象的引用被視為對其基類的引用。更重要的是,在代碼中遇到這樣的構造,編譯器會自動隱式地執行轉換。示例代碼顯示,賦值運算符( Dancer )左側聲明的類型有多種形式(類型),在右側聲明(BreakdancerElectricBoogieDancer). 相對於超類(dance方法)中定義的一般功能,每種形式都可以有自己獨特的行為。也就是說,在超類中聲明的方法可能在其後代中以不同方式實現。在這種情況下,我們正在處理方法覆蓋,這正是創建多種形式(行為)的原因。這可以通過運行 main 方法中的代碼看到: 程序輸出: 我是 Fred。我今年18歲了。我和其他人一樣跳舞。我是傑 我今年 19 歲。我霹靂舞!我是瑪西婭。我20歲。我跳電動布吉舞! 如果我們不重寫子類中的方法,那麼我們將不會得到不同的行為。例如,ElectricBoogieDancer類,那麼程序的輸出將是這樣的: 我是 Fred。我今年18歲了。我和其他人一樣跳舞。我是傑 我今年 19 歲。我和其他人一樣跳舞。我是瑪西婭。我20歲。我和其他人一樣跳舞。這意味著創建BreakdancerElectricBoogieDancer類 根本沒有意義。多態性的原理具體體現在哪裡呢?在不知道其特定類型的情況下,程序中在哪裡使用了一個對象?在我們的示例中,它發生在Dancer d對像上調用dance()方法時。在 Java 中,多態意味著程序不需要知道對像是否是一個 霹靂舞者ElectricBoogieDancer。重要的是它是Dancer類的後代。如果你提到後代,你應該注意到 Java 中的繼承不僅僅是extends,還有implements. 現在是時候提一下 Java 不支持多重繼承——每種類型都可以有一個父類(超類)和無限數量的後代(子類)。因此,接口用於向類添加多組功能。與子類(繼承)相比,接口與父類的耦合度較低。它們的使用非常廣泛。在Java中,接口是引用類型,所以程序可以聲明一個接口類型的變量。現在是舉個例子的時候了。創建接口:

public interface CanSwim {
    void swim();
}
為了清楚起見,我們將採用各種不相關的類並讓它們實現接口:

public class Human implements CanSwim {
    private String name;
    private int age;

    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void swim() {
        System.out.println(toString()+" I swim with an inflated tube.");
    }

    @Override
    public String toString() {
        return "I'm " + name + ". I'm " + age + " years old.";
    }

}
 
public class Fish implements CanSwim {
    private String name;

    public Fish(String name) {
        this.name = name;
    }

    @Override
    public void swim() {
        System.out.println("I'm a fish. My name is " + name + ". I swim by moving my fins.");

    }

public class UBoat implements CanSwim {

    private int speed;

    public UBoat(int speed) {
        this.speed = speed;
    }

    @Override
    public void swim() {
        System.out.println("I'm a submarine that swims through the water by rotating screw propellers. My speed is " + speed + " knots.");
    }
}
主要方法:

public class Main {

    public static void main(String[] args) {
        CanSwim human = new Human("John", 6);
        CanSwim fish = new Fish("Whale");
        CanSwim boat = new UBoat(25);

        List<swim> swimmers = Arrays.asList(human, fish, boat);
        for (Swim s : swimmers) {
            s.swim();
        }
    }
}
調用接口中定義的多態方法的結果向我們展示了實現該接口的類型的行為差異。在我們的例子中,這些是swim方法顯示的不同字符串。在研究了我們的示例之後,面試官可能會問為什麼在main方法 中運行這段代碼

for (Swim s : swimmers) {
            s.swim();        
}
導致我們子類中定義的覆蓋方法被調用?程序運行時如何選擇方法的期望實現?要回答這些問題,您需要解釋延遲(動態)綁定。綁定是指在一個方法調用和它具體的類實現之間建立一個映射。本質上,代碼決定了類中定義的三個方法中的哪一個將被執行。Java 默認使用後期綁定,即綁定發生在運行時,而不是像早期綁定那樣發生在編譯時。這意味著當編譯器編譯這段代碼時

for (Swim s : swimmers) {
            s.swim();        
}
它不知道哪個類(HumanFishUboat )具有游泳時將執行的代碼方法被調用。由於動態綁定機制(在運行時檢查對象的類型並為此類型選擇正確的實現),這僅在程序執行時確定。如果有人問你這是如何實現的,你可以回答說,當加載和初始化對象時,JVM 在內存中構建表,並將變量與它們的值和對象與它們的方法鏈接起來。在這樣做時,如果一個類被繼承或實現了一個接口,那麼首要任務是檢查是否存在被覆蓋的方法。如果有,它們將綁定到此類型。如果不是,則匹配方法的搜索將移動到更高一級的類(父類),依此類推,直到多級層次結構中的根。當談到 OOP 中的多態性及其在代碼中的實現時,我們注意到使用抽像類和接口來提供基類的抽象定義是一種很好的做法。這種做法遵循抽象原則——識別常見的行為和屬性並將它們放在抽像類中,或者只識別常見的行為並將其放在接口中。設計和創建基於接口和類繼承的對象層次結構是實現多態性所必需的。關於 Java 中的多態性和創新,我們注意到從 Java 8 開始,在創建抽像類和接口時可以使用 或僅識別常見行為並將其放入界面中。設計和創建基於接口和類繼承的對象層次結構是實現多態性所必需的。關於 Java 中的多態性和創新,我們注意到從 Java 8 開始,在創建抽像類和接口時可以使用 或僅識別常見行為並將其放入界面中。設計和創建基於接口和類繼承的對象層次結構是實現多態性所必需的。關於 Java 中的多態性和創新,我們注意到從 Java 8 開始,在創建抽像類和接口時可以使用default關鍵字為基類中的抽象方法編寫默認實現。例如:

public interface CanSwim {
    default void swim() {
        System.out.println("I just swim");
    }
}
有時面試官會問基類中的方法必須如何聲明才能不違反多態性原則。答案很簡單:這些方法不能是staticprivatefinal。Private 使方法僅在類中可用,因此您將無法在子類中覆蓋它。靜態將方法與類而不是任何對象相關聯,因此將始終調用超類的方法。final 使方法不可變並且對子類隱藏。

多態性給了我們什麼?

您也很可能會被問及多態性如何使我們受益。你可以簡短地回答這個問題,而不會陷入繁瑣的細節中:
  1. 它使得替換類實現成為可能。測試是建立在它之上的。
  2. 它促進了可擴展性,使得創建可在未來構建的基礎變得更加容易。在現有類型的基礎上添加新類型是擴展 OOP 程序功能的最常用方法。
  3. 它允許您將具有共同類型或行為的對象組合到一個集合或數組中並統一處理它們(如在我們的示例中,我們強迫每個人跳舞()游泳() :)
  4. 創建新類型的靈活性:您可以選擇父類的方法實現或在子類中覆蓋它。

一些臨別的話

多態性是一個非常重要和廣泛的話題。它是這篇關於 Java 中的 OOP 的文章的幾乎一半的主題,並且構成了該語言基礎的重要部分。你將無法避免在面試中定義這個原則。如果你不知道或不明白,面試很可能就結束了。所以不要偷懶——在面試前評估你的知識,並在必要時復習。

更多閱讀:

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION