多态都不知道,谈什么对象

前言

封装、继承、多态做为 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
相关文章
相关标签/搜索