多态性

275 阅读5分钟

多态的定义

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

多态是面向对象的三大特征之一,可见其重要程度。

学习资料

多态有三种形式

  • 子类型多态
  • 参数多态
  • 特设多态

子类型多态

这种多态形式是最常见的,也是目前面向对象编程中最常使用的。

子类型多态有三个前提条件

  • 继承
  • 派生类重写了基类的方法
  • 基类引用指向派生类对象

子类型多态的实现是:在基类中定义一个方法,派生类继承自基类就自动获得了该方法,在继承的同时派生类可以根据自身情况重写此方法。

在实际运行中,虽然是同一个方法,但实际调用不同会执行不同的方法实现从而产生不同的结果。

举个例子 有一个类 Anmial 中定义了两个方法。

open class Animal(){

    open fun eat(){
        println("动物在吃饭。")
    }
    open fun sleep(){
        println("动物在睡觉")
    }
}

两个派生类 Cat 和 Dog 继承自 Animal 类分别根据自身情况覆写了相应的方法。

//Cat 继承 Animal 
class Cat:Animal(){
    override fun eat() {
        println("猫在吃饭。")
    }
}
//Dog 继承 Animal 
class Dog:Animal(){
    override fun eat() {
        println("狗在吃饭。")
    }
    override fun sleep() {
        println("狗在睡觉")
    }
}

编写测试代码,在运行时更改实际对象,父类引用指向子类对象

var animal = Animal()
println("--创建了动物对象")
animal.eat() //动物在吃饭。
animal.sleep() //动物在睡觉
animal= Cat() //运行时更改了引用,实际指向了派生类的对象。
println("--运行时,赋值了猫对象")
animal.eat()//猫在吃饭。
animal.sleep()//动物在睡觉
println("--运行时,更改了狗对象")
animal = Dog()
animal.eat()//狗在吃饭。
animal.sleep()//狗在睡觉

实际输出结果,是根据运行时实际的对象产生的。

当 animal 的实际引用是 Cat 时,就会调用 在 Cat 类中覆写的方法。

如果没有覆写,则会调用基类默认的实现,如 Cat 类中就没有覆写 sleep 方法。

在实际对象为 Dog 类对象时,实际上都是调用的 Dog 类中的实现。

子类还有一种使用方式是当作参数,形参使用基类,实参可以是派生类。

举个例子 定一个方法,形参是 Animal 类型的

fun eat(animal: Animal){
    animal.eat()
}

在实际使用时,派生自 Animal的类都可以。

val dog = Dog()
eat(dog) 

根据面向对象设计原则的 里氏替换原则,任何引用基类的地方,子类都可以透明替换而不会产生异常和错误。

这就是子类型多态,同一个方法,在运行时会根据实际对象调用对应的实现,从而会产生不同的结果。

参数多态

也称 泛型多态

参数多态在程序设计语言和类型论中是指声明与定义函数、复合类型、变量时不指定其具体的类型,而是把这部分类型作为参数使用,使得该定义对各种具体类型都适用, 所以它建立在运行时的参数基础上,并且所有这些都是在不影响类型安全的前提下进行的。 -引用自 《Kotlin 核心编程》

简单来讲:在定义类型或者某些类型实现(类、函数,变量)时保留类型参数,当使用时由程序员或者编译器补上类型参数。

参数化多态使得语言更具表达力,同时保持了完全的静态类型安全。这被称为泛型函数、泛型数据类型、泛型变量,形成了泛型编程的基础。

最常见参数多态的形式就是泛型了。

在 Java 中最典型的应用就是 List 容器了,它有着一系列的通用操作模版,如 get, remove 等。

因为 List 是不变的,List 和 List 虽然是同一个类,但确是不同的类型,它们有着一样的操作和接口,并且保证类型安全。

参数多态更多的是提供一个模版,可以供一批类型使用,List 容器只是其中一个应用,更多实际应用可以多多了解一下泛型编程。

特设多态

一个多态函数是有多个不同的实现,依赖于实参而调用相应版本的函数。

简而言之就是多态函数可以根据实际参数调用不同的实现。

特设多态和泛型多态是相对的,泛型多态可以一次性的针对一批类型实现多态,而特设多态对每个类型需要专门去实现。

相对于更通用的参数多态,特设多态提供了 “量身定制”的能力。

重载函数以及运算符重载就是 特设多态的一种形式。

举个例子,

fun add(a:Int,b:Int) = a+b
fun add(a:Float,b:Float) = a+b
fun add(a:String,b:String) = a+b

一个 add () 方法,在参数是 Int 或者 Float 类型时会调用不同的函数版本。

每个函数都是针对某种类型专门写的。

而运算符重载则也一样,都是为了某种类型特别编写的,所以一般说特设多态和参数多态是相对的。

前者是量身定制,后者是批量通用。


最近在学习一本书 《Kotiln 核心编程》在看到多态的时候看到这三种多态,有必要学习记录一下,特此记录。

在学习过程中,发现关于参数多态和特设多态的学习资料很少,如果有大神看到希望能留下一些资料,谢谢!