多態都不知道,談什麼對象

前言

封裝、繼承、多態做爲 OOP 世界的老三樣,幾乎是必背的關鍵詞。java

而在剛學習 Java 的很長一段時間,我對多態的理解一直處理很迷糊的狀態,重載是多態嗎?泛型是多態嗎?繼承關係是多態嗎?程序員

實際上都是,不管重載、泛型,仍是繼承關係都是多態的一個具體體現,也被歸屬爲不一樣的多態分類編程

  • Ad hoc polymorphism(特定多態,也譯做特設多態)
  • Parametric polymorphism(參數化多態)
  • Subtyping(子類型多態)

固然不止上面三種分類,像 Scala 就還有另一種多態分類安全

  • Row polymorphism(行多態)

別被這些名詞概念唬住,下面咱們就經過代碼實例來一一過一遍。markdown

Ad hoc polymorphism(特定多態)

特定多態是由 Christopher Strachey 在 1967 年提出來的,從它的取名咱們能夠大概猜到,它是針對於特定問題的多態方案,好比:編程語言

  • 函數重載
  • 操做符重載

函數重載指的是多個函數擁有相同的名稱,但卻擁有不一樣的實現。ide

好比下面的函數重載示例,展現了兩個名爲 print 的 函數,一個打印字符串,一個打印圖像。函數

public void print(String word) {
  ...
}

public void print(Image image) {
  ...
}
複製代碼

操做符重載本質上是一個語法糖,實際的體驗與函數重載類似,好比 Java 中的 + 就是一個能夠重載的操做符工具

實際上 Java 的 + 不徹底算是操做符重載,由於它針對於字符串的操做實際上是將 + 轉譯成了 StringBuilder 來處理的,算是語法糖。oop

但仍能夠藉助於它來理解操做符重載

1 + 1

1 + 1.3
  
"hello" + " world"
 
1 + " ok"
複製代碼

編譯器會根據 + 左右兩邊的類型執行不一樣的操做

  • 1 + 1 執行求和運算,返回值爲 int
  • 1 + 1.3 執行求和運算,返回值提高爲 double
  • "hello" + " world" 執行字符串拼接,返回值爲 String
  • 1 + " ok" 執行字符串拼接, 返回值爲 String

Java 對操做符的重載是有必定限制的(只有內建的操做符重載),而 Scala 則更開放一些,容許開發者自定義操做符重載。

Parametric polymorphism(參數化多態)

參數化多態和特定多態都是同一年由同一人提出的,最開始由 ML 語言實現(1975年),時至今日,幾乎全部的現代化語言都有對應特性進行支持,好比 D 和 C++ 中的模板,C#、Delphi 和 Java 中的泛型。

對於它的好處,我從 wiki 摘錄了一段

參數化多態使得編程語言在保留了靜態語言的類型安全特性的同時又加強了其表達能力

以 Java 的集合庫 List<T> 爲例,List 就是類型構造器, T 就是參數,經過指定不一樣的參數類型就實現了多態

  • List<String>
  • List<Integer>

若是不考慮類型擦除,List<String>List<Integer> 就是兩個不一樣的類型,從這兒咱們也能夠看出,參數化多態能夠適用於無窮多的類型。

wiki 上有一段描述參數化多態與特定多態的區別我以爲很是形象

假如咱們要把原材料切成兩半——

  • 參數多態:只要能 「切」 ,就用工具來切割它。
  • 特定多態:根據原材料是鐵仍是木頭仍是什麼來選擇不一樣的工具來切。

Subtyping(子類型多態)

子類型多態有時也被稱之爲包含多態(inclusion polymorphism),它表達的是一種替代關係。

如下面的 Java 代碼爲例, Car 分別有 SmallCar、BigCar 兩個子類

abstract class Car {}

class SmallCar extends Car {}

class BigCar extends Car {}
複製代碼

那麼在 priceOfCar函數內,BigCar 和 SmallCar 就是能夠相互替換的了

public BigDecimal priceOfCar(Car car) {
	//TODO
}
複製代碼

要注意子類型繼承這二者之間是不同的。

子類型更多的是描述一種關係:若是 S 是 T 的子類型,即 S <: T ),那麼在任何使用 T 類型的上下文中也均可以使用 S,至關於 S 能夠替換掉 T。(有沒有想起里氏替換原則 ?)

<: 是來源於類型論的符號,表示子類型關係

而繼承則是編程語言的一種特性,換句話說,就是經過繼承描述了子類型關係。

Row polymorphism(行多態)

Row polymorphism子類型多態很類似,但倒是一個大相徑庭的概念,它主要用於處理結構化類型的多態。

那麼問題來了,什麼是結構化類型呢?

在 Java 中咱們判斷類型是否兼容或對等是根據名稱來的,這種類型系統通常被稱之爲基於名稱的類型(或標明類型系統), 而結構化類型則是基於類型的實際結構或定義來判斷類型的兼容性和對等性。

因爲 Java 不支持 Row Polymorphism,因此下面會用 Scala 來進行展現。

假設咱們如今有一個特質 (相似於 Java 的接口) Event ,它是對業務事件的抽象, EventListener 則是事件的處理類, 它的 listen 函數接受 Event 對象做爲參數。

trait Event {
    def payload(): String
}

class InitEvent extends Event {
  override def payload(): String = {
    // TODO
  }
}

class EventListener {
    def listen(event: Event): Unit = {
        //TODO
    }
}
複製代碼

正常狀況下咱們會這樣來使用

val listener = new EventListener()
listener.listen(new InitEvent())
複製代碼

若是此時有一個 OldEvent,它沒有實現 Event 特質 , 但卻有相同的 payload 方法定義

class OldEvent {
  def palyload() = {
    // TODO
  }
}
複製代碼

熟悉 Java 的同窗都知道,EventListener 是沒法接受 OldEvent 對象的 ,由於 OldEvent 不是 Event 的子類

// 編譯失敗: OldCall 不是 Event 類型
listener.listen(new OldCall())
複製代碼

再來看看結構化類型在該場景下的表現,將 EventListener 的 listen 函數參數由 Event 類改成結構化對象

class EventListener {
    /** * event 是結構類型的名稱 * {def payload(): String} 表示這個結構的具體定義:擁有一個無參,返回值爲 String 的 payload 函數 */
  def listen(event: {def payload(): String}) {
  	// TODO
  }
}
複製代碼

之前是隻接受類型爲 Event 的對象,如今是接受有 payload 函數定義的結構對象就能夠了

// 編譯經過
listener.listen(new InitEvent())
// 編譯經過
listener.listen(new OldEvent())
複製代碼

即便 OldEvent 裏面的方法不止一個 payload 也是沒問題的(結構的部分匹配)

class OldEvent {
  
  def payload(): String = {}
  
  def someOtherMehod = {}

}

// 編譯經過
listener.listen(new OldEvent())
複製代碼

正是由於部分匹配的特性,結構化多態也常常被稱之爲類型安全的鴨子類型(duck typing

最後

若是你仍是爲上面的概念而感到混亂,那麼就忘了他們吧,只須要記住咱們日常使用的函數重載、繼承、泛型等都是多態的具體體現便可。

回到標題,如今大家都知道多態了,那麼放心的去談對象吧......

什麼?沒有對象,本身 new 一個呀(程序員老梗~)

參考

  1. Polymorphism (computer science)
  2. Ad_hoc_polymorphism
  3. Parametric polymorphism
  4. Subtyping
  5. Row polymorphism
  6. nominative type system
  7. structural type system
相關文章
相關標籤/搜索