什么是多態(tài)铸屉?不同語言實現(xiàn)的方式

什么是多態(tài)

面向對象程序設計有三要素:封裝、繼承(或組合)切端、多態(tài)彻坛,前兩者較好理解,多態(tài)總讓人困惑踏枣,不知道具體有什么作用昌屉,更不知道為什么要用多態(tài)。今天就來詳細分析下什么是多態(tài)茵瀑,以及多態(tài)有哪些好處间驮,為什么要用多態(tài)?


多態(tài).png

多態(tài)是指同一行為作用于不同對象時马昨,可以表現(xiàn)出多種不同的形式和結果來竞帽。例如,子類繼承父類并覆蓋其方法后鸿捧,用父類引用指向子類對象并調用該方法時屹篓,實際執(zhí)行的是子類的方法。

這種根據(jù)對象實際類型而非聲明類型來確定執(zhí)行方法的行為匙奴,就是多態(tài)性的體現(xiàn)堆巧。多態(tài)主要通過繼承和接口實現(xiàn),允許同一接口有多種不同的實現(xiàn)方式。

多態(tài)的分類

  • 編譯時多態(tài)谍肤,又稱靜態(tài)綁定啦租,是指編譯器在編譯時通過檢查引用類型的方法是否存在,來定位到相應的類及其方法荒揣,而不檢查實際對象是否支持該方法篷角。編譯時多態(tài)主要體現(xiàn)在方法重載上,即根據(jù)參數(shù)類型乳附、數(shù)量和順序内地,在編譯時確定要執(zhí)行的方法。

  • 運行時多態(tài)赋除,又稱動態(tài)綁定阱缓,是指程序在運行時根據(jù)對象的實際類型來確定調用哪個方法,而不是在編譯時確定举农。這意味著方法的具體實現(xiàn)取決于對象的實際類型荆针,而非其聲明類型。父類引用可以指向不同的子類對象颁糟,使得相同方法調用產(chǎn)生不同的行為結果航背。通過運行時確定具體執(zhí)行的方法,代碼具有更好的擴展性和可維護性棱貌。

多態(tài)的實現(xiàn)方式

編譯時多態(tài)玖媚,方法重載(Overloading):

重載指在同一個類中可以有多個方法,這些方法名稱相同但參數(shù)列表不同(參數(shù)數(shù)量或類型不同)婚脱。

編譯器在編譯階段就能確定具體的方法今魔。以下是一個重載示例,展示了多個同名方法障贸,但參數(shù)個數(shù)或類型不同错森。重載的好處是簡化接口設計,不需要為不同類型編寫多個方法名篮洁。

// OverloadExample.java  全部源碼見文檔鏈接
/**
 * 重載示例涩维,同名方法,參數(shù)個數(shù)或類型不同袁波。
 * 編譯器在編譯時確定具體的調用方法瓦阐。
 */
class Calculator {
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    public int add(int... nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        return sum;
    }
}

運行時多態(tài),方法重寫(Override)與轉型(Casting):

運行時多態(tài)是在程序運行時確定實際要執(zhí)行的方法锋叨。

當子類繼承父類并覆蓋同名方法時垄分,這稱為重寫。使用父類引用來聲明子類對象時娃磺,子類會向上轉型為父類類型薄湿。調用該對象的方法時,實際執(zhí)行的是子類的方法,而不是父類的方法豺瘤。

向上轉型是指使用父類引用聲明子類對象吆倦,使子類對象的實際類型變?yōu)楦割悺Mㄟ^父類引用調用子類的方法坐求,使代碼更加通用蚕泽,處理一組相關對象時無需知道它們的具體類型。

向下轉型則是將父類引用轉換為子類引用桥嗤,這需要顯式進行须妻,并且在轉換前需要使用 instanceof 關鍵字進行類型檢查。

// OverrideExample.java 全部源碼見文檔鏈接
/**
 * 重寫示例泛领,子類覆蓋父類同名方法荒吏,體現(xiàn)多態(tài)。
 * 子類向上轉型為父類型渊鞋,父類強制向下轉型為子類型绰更。
 */
class Shape {
  void draw() {
    System.out.println("Shape->draw");
  }

  void drawShape() {
    System.out.println("Shape->drawShape");
  }
}

class Circle extends Shape {
  @Override
  void draw() {
    System.out.println("Circle->draw");
  }

  void drawCircle() {
    System.out.println("Circle->drawCircle");
  }
}

class Square extends Shape {
  @Override
  void draw() {
    System.out.println("Square->draw");
  }

  void drawSquare() {
    System.out.println("Square->drawSquare");
  }
}

public class OverrideExample {
  public static void main(String[] args) {
    // 用父類引用聲明子類對象,向上轉型
    Shape shape1 = new Circle();
    Shape shape2 = new Square();

    // 子類有同名方法锡宋,動態(tài)綁定到子類儡湾,實質執(zhí)行的是Circle.draw(),體現(xiàn)多態(tài)
    shape1.draw();

    // 報錯执俩,編譯時按聲明類型檢查徐钠,Shape類中沒有drawCircle方法
    // shape1.drawCircle();

    // 執(zhí)行父類方法,輸出 "Shape->drawShape"
    shape1.drawShape();

    if (shape2 instanceof Square) {
      // 向下轉型役首,用子類重新聲明丹皱,成為子類型了
      Square mySquare = (Square) shape2;

      // 輸出 "Square->draw"
      mySquare.draw();

      // 輸出 "Square->drawSquare"
      mySquare.drawSquare();

      // 報錯。若強轉為父類型宋税,則無法調用drawSquare方法
      // ((Shape) mySquare).drawSquare();

      // 繼承父類,輸出 "Shape->drawShape"
      mySquare.drawShape();
    }
  }
}

多態(tài)三個必要條件:

嚴格來說讼油,多態(tài)需要具備以下三個條件杰赛。
繼承:子類繼承父類或實現(xiàn)接口。
重寫:子類覆蓋父類的方法矮台。
父類聲明子類:使用父類引用來聲明子類對象乏屯。
重載不屬于嚴格意義上的多態(tài),因為重載在編譯階段就確定了瘦赫。這里主要探討運行時的多態(tài)辰晕,即針對某個類型的方法調用,取決于運行時的對象确虱,而不是聲明時的類型含友。

// 父類
class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

// 子類繼承并重寫同名方法
class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }
}

public class Test {
    public static void main(String[] args) {
         // 父類引用聲明子類
        Animal myAnimal = new Dog();
         // 運行時對象為子類,故輸出"Dog barks"
        myAnimal.makeSound();
    }
}

如何理解父類聲明子類 Parent child = new Child();

  • 解釋:用 Parent 類聲明了一個 child 引用變量(變量存于棧中)窘问,并賦值為 Child 實例對象(對象存于堆中)辆童。變量 child 的類型為 Parent(向上轉型),它的值是一個 Child 類型的實例對象惠赫。

  • 加載執(zhí)行順序:
    編譯時:JVM 編譯時檢查類的關系和對應方法(包括重載)把鉴,確定變量的類型并定位相關方法名稱,生成字節(jié)碼儿咱。
    運行時

    1. JVM 加載 Parent 和 Child 類庭砍。
    2. 根據(jù) Parent 和 Child 的大小分配堆內存。
    3. 初始化 new Child() 并返回對象引用混埠。
    4. 分配棧內存給變量 child怠缸。
    5. 將對象引用賦值給 child。
  • 總結:
    編譯時根據(jù)引用類型(不是實例對象)確定方法的名稱和參數(shù)(包括重載)岔冀。
    運行時如果子類覆蓋了父類的方法凯旭,則調用子類(實例引用類型)的方法;如果沒有覆蓋使套,則執(zhí)行父類(變量引用類型)的方法罐呼。

多態(tài)的好處,為什么要用多態(tài)侦高?

在面向對象設計中嫉柴,“開閉原則”是非常重要的一條。即系統(tǒng)中的類應該對擴展開放奉呛,而對修改關閉计螺。這樣的代碼更可維護和可擴展,同時更加簡潔與清晰瞧壮。

延續(xù)上面的例子登馒,假設業(yè)務需要擴充更多子類,我們可以通過以下步驟來體現(xiàn)開閉原則:

  1. 新增子類:根據(jù)業(yè)務需求咆槽,新增符合現(xiàn)有類層次結構的子類陈轿,例如增加AnotherChild。

  2. 繼承和重寫:新的子類應該繼承自適當?shù)母割惽胤蓿⒏鶕?jù)需要重寫父類的方法或添加新的方法麦射。

  3. 不需要修改現(xiàn)有的代碼:遵循開閉原則,我們不修改現(xiàn)有的 Parent 和 Child 類的代碼灯谣。

  4. 使用多態(tài):通過父類引用來聲明子類潜秋,例如 Parent child = new AnotherChild();,這樣代碼中現(xiàn)有的邏輯不需要改變胎许。

  5. 編譯時不變性:編譯時確定方法調用的特性不改變峻呛,仍然根據(jù)引用類型來確定方法的名稱和參數(shù)罗售,子類隨意增加,只要覆蓋父類同名方法即可杀饵。

  6. 運行時多態(tài)性:運行時根據(jù)實際對象的類型來決定要執(zhí)行的方法莽囤,這使得代碼具有良好的可擴展性和可維護性。

// 定義一個通用Animal類
class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

// 定義Dog類切距,它是動物的子類
class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }
}

// 定義Cat類朽缎,它是動物的子類
class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Cat meows");
    }

    // Cat自有方法
    void meow() {
        System.out.println("Cat is meowing...");
    }
}

// 定義一個動物園類,管理不同的動物
class Zoo {
    // 傳入的是抽象父類或接口谜悟,方便擴展
    void letAnimalMakeSound(Animal animal) {
        animal.makeSound();
    }
}

public class AnimalExample {
    public static void main(String[] args) {
        Zoo zoo = new Zoo();

        Animal myDog = new Dog(); // 向上轉型
        Animal myCat = new Cat(); // 向上轉型
        ((Cat)myCat).meow(); // 向下強轉话肖,打印自有方法

        // 通過多態(tài)性,動物園可以使用相同的方法處理不同種類的動物
        zoo.letAnimalMakeSound(myDog); // 輸出 "Dog barks"
        zoo.letAnimalMakeSound(myCat); // 輸出 "Cat meows"
    }
}

要增加新的動物(如鳥類葡幸,Bird)最筒,只需擴展 Animal 類,而無需修改現(xiàn)有 Zoo 類中的方法蔚叨。

class Bird extends Animal {
    @Override
    void makeSound() {
        System.out.println("Bird chirps");
    }
}

public class AnimalExample {
    public static void main(String[] args) {
        Zoo zoo = new Zoo();

        Animal myDog = new Dog(); // 向上轉型
        Animal myCat = new Cat(); // 向上轉型
        Animal myBird = new Bird(); // 向上轉型

        // 通過多態(tài)性床蜘,動物園可以使用相同的方法處理不同種類的動物
        zoo.letAnimalMakeSound(myDog); // 輸出 "Dog barks"
        zoo.letAnimalMakeSound(myCat); // 輸出 "Cat meows"
        zoo.letAnimalMakeSound(myBird); // 輸出 "Bird chirps"
    }
}

這種設計:

  • 允許新增 Animal 的子類,保持對擴展開放蔑水;
  • 無需修改依賴 Zoo 的 letAnimalMakeSound 方法邢锯,實現(xiàn)對修改封閉。

我們的業(yè)務總在不停變化搀别,如何使得代碼底層不用大概丹擎,而表層又能跟隨業(yè)務不停變動,這就顯得十分重要歇父。通過這種方式蒂培,我們在不修改現(xiàn)有代碼的情況下,可以輕松地引入新的子類并擴展系統(tǒng)功能榜苫,同時保持現(xiàn)有代碼的穩(wěn)定性和可靠性护戳。

其他語言如何實現(xiàn)多態(tài)?

不同語言因為語言特性的不同垂睬,在實現(xiàn)多態(tài)上也略有不同灸异。不過總的概念是一致的,即達到“開閉原則”的目標羔飞。以下一些語言的例子,其他例子請從倉庫查找源碼檐春。

Go語言例子

在Go語言中逻淌,雖然沒有傳統(tǒng)意義上的類繼承、父類聲明子類和方法重載疟暖,但通過結構體(struct)和接口(interface)以及匿名組合等方式實現(xiàn)類似的功能卡儒。這樣也能實現(xiàn)代碼的組織和復用田柔,同時保持了靈活性和簡潔性。

package main

import (
  "fmt"
)

// 定義一個Animal接口
type Animal interface {
  MakeSound()
}

// 定義一個 Dog 類型
type Dog struct{}

// 實現(xiàn) Animal 接口的 MakeSound 方法
func (d Dog) MakeSound() {
  fmt.Println("Dog barks")
}

// 定義一個 Cat 類型
type Cat struct{}

// 實現(xiàn) Animal 接口的 MakeSound 方法
func (c Cat) MakeSound() {
  fmt.Println("Cat meows")
}

// Cat自有方法
func (c *Cat) Meow() {
  fmt.Println("Cat is meowing...")
}

// 定義一個 Zoo 類型骨望,用于管理動物
type Zoo struct{}

// 定義一個方法硬爆,讓動物發(fā)出聲音
func (z Zoo) LetAnimalMakeSound(a Animal) {
  a.MakeSound()
}

func main() {
  zoo := Zoo{}
  myDog := Dog{}
  // 接口斷言
  var myCat Animal = &Cat{}
  // 類型斷言,打印自有方法
  (myCat.(*Cat)).Meow()

  // 使用多態(tài)性擎鸠,通過接口類型處理不同的具體類型
  zoo.LetAnimalMakeSound(myDog) // 輸出 "Dog barks"
  zoo.LetAnimalMakeSound(myCat) // 輸出 "Cat meows"
}

當需要增加Bird類型時缀磕,直接增加即可。同樣無需修改Zoo類里面的LetAnimalMakeSound方法劣光。

type Bird struct{}

// 實現(xiàn) Animal 接口的 MakeSound 方法
func (b Bird) MakeSound() {
    fmt.Println("Bird chirps")
}

func main() {
  zoo := Zoo{}
  myDog := Dog{}
  var myCat Animal = &Cat{}
  (myCat.(*Cat)).Meow()
  myBird := Bird{}

  // 使用多態(tài)性袜蚕,通過接口類型處理不同的具體類型
  zoo.LetAnimalMakeSound(myDog)  // 輸出 "Dog barks"
  zoo.LetAnimalMakeSound(myCat)  // 輸出 "Cat meows"
  zoo.LetAnimalMakeSound(myBird) // 輸出 "Bird chirps"
}

嚴格的多態(tài)概念,包括子類繼承父類绢涡、方法重寫以及父類聲明子類等牲剃,這些特性在Go語言中無法實現(xiàn)。Go語言沒有class概念雄可,雖然它的struct可以包含方法凿傅,看起來像class,但實際上沒有繼承和重載的支持数苫,它們本質上仍是結構體聪舒。

Go語言摒棄了傳統(tǒng)面向對象語言中的class和繼承概念,我們需要用新的視角來理解和實踐面向對象編程在Go中的應用方式

JavaScript語言例子

JavaScript是一種動態(tài)弱類型的基于對象的語言文判,其一切皆是對象过椎。它通過對象的原型鏈來實現(xiàn)面向對象編程。盡管JavaScript具有class和繼承的能力戏仓,但由于缺少強類型系統(tǒng)疚宇,因此無法實現(xiàn)傳統(tǒng)意義上的多態(tài)。

當然赏殃,JavaScript作為動態(tài)語言敷待,具有天然的動態(tài)性優(yōu)勢。這使得它在靈活性和擴展性方面更具優(yōu)勢仁热。

// 定義一個通用Animal類
class Animal {
    makeSound() {
        console.log("Animal makes a sound");
    }
}

// 定義Dog類榜揖,它是動物的子類
class Dog extends Animal {
    makeSound() {
        console.log("Dog barks");
    }
}

// 定義Cat類,它是動物的子類
class Cat extends Animal {
    makeSound() {
        console.log("Cat meows");
    }
    // Cat自有函數(shù)
    meow() {
        console.log("Cat is meowing...", this);
    }
}

// 定義一個動物園類抗蠢,管理不同的動物
class Zoo {
    // JS沒有嚴格類型举哟,出原始數(shù)據(jù)類型外,其他均是Object
    // 說出傳入的對象只要有makeSound方法即可迅矛。
    letAnimalMakeSound(animal) {
        animal.makeSound();
    }
}

// 測試代碼
const zoo = new Zoo();
// JS沒有父類定義子類概念妨猩,直接聲明即可,無需向上轉型
// 通過instanceof類型判斷時可得到子類和父類類型
const myDog = new Dog();
const myCat = new Cat();

// 直接調用自有函數(shù)
myCat.meow();

// 可以動態(tài)給對象設置函數(shù)并綁定對象 
myDog.meow = myCat.meow.bind(myDog); 
myDog.meow();

// 動物園可以使用相同的方法處理不同種類的動物
// 當需要增加其他動物時秽褒,直接建立新的類繼承Animal壶硅,而無需修改Zoo威兜。
zoo.letAnimalMakeSound(myDog); // 輸出 "Dog barks"
zoo.letAnimalMakeSound(myCat); // 輸出 "Cat meows"

可以看出JS要實現(xiàn)Java意義的多態(tài)是做不到的,但JavaScript更加靈活方便庐椒,聲明對象無需類型椒舵,還可以動態(tài)添加函數(shù)和綁定對象。

Python語言例子

# 定義一個通用Animal類  
class Animal:  
    def make_sound(self):  
        print("Animal makes a sound")  
  
# 定義Dog類约谈,繼承Animal
class Dog(Animal):  
    name = "Dog"
    def make_sound(self):  
        print("Dog barks")  
  
# 定義Cat類笔宿,繼承Animal
class Cat(Animal):  
    name = "Cat"
    def make_sound(self):  
        print("Cat meows")  
  
    # Cat自有方法  
    def meow(self):  
        print(self.name + " is meowing...")  
  
# 定義Bird類,它是動物的子類  
class Bird(Animal):  
    def make_sound(self):  
        print("Bird chirps")  

# 定義管理類
class Zoo:  
    # python與js一樣為動態(tài)語言窗宇,使用duck typing措伐,不需要顯式聲明接口
    def let_animal_make_sound(self, animal):  
        animal.make_sound()  
  
# 測試代碼
if __name__ == "__main__":
    zoo = Zoo()

    # 直接創(chuàng)建實例,Python中不需要向上轉型
    my_dog = Dog()
    my_cat = Cat()
    my_bird = Bird()

    # 直接調用自有方法
    my_cat.meow()

    # Python中可直接給對象設置方法军俊,self不會改變
    my_dog.meow = my_cat.meow
    my_dog.meow()

    # 動物園可以使用相同的方法處理不同種類的動物
    zoo.let_animal_make_sound(my_dog)  # 輸出 "Dog barks"
    zoo.let_animal_make_sound(my_cat)  # 輸出 "Cat meows"
    zoo.let_animal_make_sound(my_bird)  # 輸出 "Bird chirps"

Python是一種動態(tài)語言侥加,它使用 self 參數(shù)來引用實例,無需像其他語言那樣使用 new 關鍵字來實例化對象粪躬。Python沒有嚴格的接口概念担败,不需要像其他語言那樣顯示聲明對象的接口。Python通過繼承和方法重寫來實現(xiàn)多態(tài)概念镰官,但不支持傳統(tǒng)意義上的父類聲明子類和方法重載提前。

因此,Python在多態(tài)性上的表現(xiàn)與JavaScript相似泳唠,都是基于動態(tài)語言特性狈网,靈活而動態(tài),通過繼承和重寫實現(xiàn)對象行為的多樣性笨腥。

Java多態(tài)實例詳解

理解Java多態(tài)的實例可以幫助澄清其原理和執(zhí)行過程拓哺。以下是一個簡單而詳盡的例子,幫助你全面理解Java中多態(tài)的工作機制脖母。

// PolymorphismSimple.java
// 父類A
class A {
    public String show(D object) {
        return ("A and D");
    }

    public String show(A object) {
        return ("A and A");
    }

    // 默認注釋掉士鸥。可開關注釋測試下
    // public String show(B object) {
    // return ("A and B");
    // }
}

// 子類B
class B extends A {
    public String show(B object) {
        return ("B and B");
    }

    public String show(A object) {
        return ("B and A");
    }
}

// 孫子類C
class C extends B {
}

// 孫子類D
class D extends B {
}

// 測試驗證
public class PolymorphismSimple {
    public static void main(String[] args) {
        // 父類聲明自己
        A a = new A();
        // 父類聲明子類
        A ab = new B();
        // 子類聲明自己
        B b = new B();
        C c = new C();
        D d = new D();

        // 1) A and A谆级。b的類型是B烤礁,也是B的實例,A里沒有show(B)方法肥照,但有show(A)方法脚仔。B的父類是A,因此定位到A.show(A)舆绎。
        System.out.println("1) " + a.show(b));

        // 2) A and A鲤脏。c的類型是C,也是C的實例亿蒸,C繼承B凑兰,B繼承A。A里沒有show(C)方法边锁,也沒有show(B)方法姑食,最后指向A.show(A)。
        System.out.println("2) " + a.show(c));

        // 3) A and D, d的類型是D茅坛,也是D的實例音半,D繼承B,B繼承A贡蓖。A里有show(D)方法曹鸠,直接定位到A.show(D)。
        System.out.println("3) " + a.show(d));

        // 4) B and A, ab是B的實例斥铺,但用A聲明彻桃,即向上轉型得到的類型是A,運行時才能確定具體該調用哪個方法晾蜘。
        // ab是B的實例對象邻眷,但引用類型是A。類型是在編譯時確定剔交,因此從類型開始定位方法肆饶。
        // A類中沒有show(B)方法,但有show(A)方法岖常,因為A是B的父類驯镊,ab也是A的實例,于是定位到A.show(A)方法竭鞍。
        // 由于B是A的子類板惑,且B重寫了A的show(A),A的方法被覆蓋了笼蛛,于是定位到B.show(A)洒放,這就是動態(tài)綁定。
        // 雖然B中有show(B)方法滨砍,但是因為ab的類型是A往湿,編譯時根據(jù)類型定位到A的方法,而不是B惋戏。

        // 以下幾種可開關打開/注釋代碼測試下领追。
        // -
        // 若A里有show(A)和show(B),B里有show(B)有show(A)响逢,則編譯時關聯(lián)到A.show(B)绒窑,因B覆蓋了A.show(B),動態(tài)綁定到B.show(B)。
        // -
        // 若A里有show(A)和show(B)闷叉,B里無show(B)有show(A)癣猾,則編譯時關聯(lián)到A.show(B)抠忘,因B無覆蓋屎媳,則直接調用A.show(B)慈省。
        // -
        // 若A里有show(A)無show(B)读存,B里無show(B)有show(A)微姊,則編譯時關聯(lián)到A.show(A)洼哎,因B覆蓋了A.show(A)烫映,動態(tài)綁定到B.show(A)。
        // -
        // 若A里有show(A)無show(B)噩峦,B里無show(A)有show(B)锭沟,則編譯時關聯(lián)到A.show(A),因B無覆蓋识补,則直接調用A.show(A)族淮。
        // 查找順序為:編譯時根據(jù)引用類型確定所屬類 -> 根據(jù)重載參數(shù)類型定位(類型按子->父->祖逐級往上查找)到類的具體方法(包括繼承的方法) ->
        // 運行時實例對象覆蓋(覆蓋只有子->父一層)了引用類型的同名方法 -> 定位到實例對象的方法。
        System.out.println("4) " + ab.show(b));

        // 5) B and A李请。ab是B的實例瞧筛,類型是A。從A類沒找到show(C)方法导盅,也沒找到A.show(B)方法较幌,找到A.show(A)方法。A.show(A)被B.show(A)覆蓋白翻,因此調用B.show(A)乍炉。
        System.out.println("5) " + ab.show(c));

        // 6) A and D。A里面有show(D)的方法滤馍,直接定位到岛琼。
        System.out.println("6) " + ab.show(d));

        // 7) B and B。B里面有show(B)的方法巢株,直接定位到槐瑞。
        System.out.println("7) " + b.show(b));

        // 8) B and B。B沒有show(c)方法阁苞,但有show(B)方法困檩。C繼承自B,父類型是B那槽,因此調用B.show(B)悼沿。
        System.out.println("8) " + b.show(c));

        // 9) A and D。B中沒有show(D)方法骚灸,B繼承A糟趾,A里有show(D), 故調用A.show(D)方法。
        System.out.println("9) " + b.show(d));

        // 10) B and A。父類聲明子類义郑,存在向上轉型蝶柿。A里有show(A),被B.show(A)覆蓋了非驮,因此定位到B.show(A)只锭。
        System.out.println("10) " + ab.show(a));

    }
}

總結

多態(tài)包括編譯時多態(tài)和運行時多態(tài)。編譯時多態(tài)院尔,即靜態(tài)綁定,通常通過方法重載實現(xiàn)喉誊。運行時多態(tài)則是在代碼運行時確定具體調用的方法邀摆。

從Java的角度看,嚴格意義上的多態(tài)需要滿足三個條件:繼承伍茄、方法覆蓋和父類引用子類對象栋盹。Java完全符合這些要求,實現(xiàn)了嚴格意義上的多態(tài)敷矫。

Go語言例获、Python和JavaScript不完全符合嚴格意義上的多態(tài),但具備多態(tài)特性曹仗,能夠達成動態(tài)確定實際要執(zhí)行的方法榨汤,從而使代碼更加靈活、易于維護和擴展怎茫。

各語言完整示例

https://github.com/microwind/design-pattern/tree/main/programming-paradigm/oop/polymorphism

簡單示例

PolymorphismSimple.java
PolymorphismSimple.go
polymorphism_simple.c
PolymorphismSimple.cpp
PolymorphismSimple.js
PolymorphismSimple.py
PolymorphismSimple.ts

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末收壕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子轨蛤,更是在濱河造成了極大的恐慌蜜宪,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祥山,死亡現(xiàn)場離奇詭異圃验,居然都是意外死亡,警方通過查閱死者的電腦和手機缝呕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門澳窑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岳颇,你說我怎么就攤上這事照捡。” “怎么了话侧?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵栗精,是天一觀的道長。 經(jīng)常有香客問我,道長悲立,這世上最難降的妖魔是什么鹿寨? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮薪夕,結果婚禮上脚草,老公的妹妹穿的比我還像新娘。我一直安慰自己原献,他們只是感情好馏慨,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姑隅,像睡著了一般写隶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讲仰,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天慕趴,我揣著相機與錄音,去河邊找鬼鄙陡。 笑死冕房,一個胖子當著我的面吹牛,可吹牛的內容都是我干的趁矾。 我是一名探鬼主播耙册,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼毫捣!你這毒婦竟也來了觅玻?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤培漏,失蹤者是張志新(化名)和其女友劉穎溪厘,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牌柄,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡畸悬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了珊佣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹋宦。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咒锻,靈堂內的尸體忽然破棺而出冷冗,到底是詐尸還是另有隱情,我是刑警寧澤惑艇,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布蒿辙,位于F島的核電站拇泛,受9級特大地震影響,放射性物質發(fā)生泄漏思灌。R本人自食惡果不足惜俺叭,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望泰偿。 院中可真熱鬧熄守,春花似錦、人聲如沸耗跛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽调塌。三九已至牍氛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烟阐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工紊扬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蜒茄,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓餐屎,卻偏偏與公主長得像檀葛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子腹缩,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內容