多态性在没有继承的情况下是可能的吗?

2022-08-31 15:24:58

在一次采访中,我被问及多态性是否可以在没有遗传的情况下实现。这可能吗?


答案 1

我读过的关于这个主题的最好的解释是卢卡·卡德利(Luca Cardelli)的一篇文章,他是一位著名的类型理论家。本文名为“了解类型、数据抽象和多态性”。

多态性的类型

Cardelli在本文中定义了几种类型的多态性:

  • 普遍
    • 参数
    • 包含
  • 临时
    • 超载
    • 强迫

与遗传相关的多态性类型分为包含多态性或亚型多态性。

维基百科提供了一个很好的定义:

在面向对象编程中,子类型多态性或包含多态性是类型理论中的一个概念,其中名称可以表示许多不同类的实例,只要它们与某些常见的超类相关。包含多态性通常通过子类型来支持,即不同类型的对象完全可以替代另一种类型的对象(其基本类型),因此可以通过公共接口进行处理。或者,可以通过类型强制(也称为类型转换)来实现包含多态性。

维基百科上另一篇名为《面向对象编程中的多态性》的文章似乎也回答了你的问题。

在爪哇语中

Java中的这种子类型功能是通过继承类和接口来实现的。尽管Java的子类型功能在继承方面可能并不总是很明显。以泛型的协方差和逆变为例。此外,数组是可序列化和可克隆的,尽管这在类型层次结构中的任何地方都不明显。也可以说,通过基元加宽转换,Java中的数字运算符是多态的,在某些情况下甚至接受完全不相关的操作数(即字符串和数字或字符串加上其他对象的串联)。还要考虑基元的装箱和拆箱的情况。后一种多态性(强制和重载)与遗传完全无关。

例子

包含

List<Integer> myInts = new ArrayList<Integer>();

这就是您的问题似乎指的是这种情况,即当类型之间存在继承或实现关系时,例如在这种情况下,ArrayList实现List。

但是,正如我所提到的,当你引入Java泛型时,有时子类型的规则会变得模糊:

List<? super Number> myObjs = new ArrayList<Object>();
List<? extends Number> myNumbers = new LinkedList<Integer>();

在其他情况下,这些关系在API中甚至不明显

Cloneable clone = new int[10];
Serializable obj = new Object[10]

即便如此,根据Cardelli的说法,所有这些都是通用多态性的形式。

参数

public <T> List<T> filter(Predicate<T> predicate, List<T> source) {
  List<T> result = new ArrayList<>();
  for(T item : source) {
    if(predicate.evaluate(item)){
         result.add(item);
    }
   return result;
  }
}

相同的算法可用于筛选具有各种谓词的所有类型的列表,而不必对每种可能的列表类型重复一行代码。实际列表的类型和谓词的类型是参数化的。请参阅此示例,其中包含 JDK 8 预览版中提供的 lambda 表达式(了解谓词实现的简洁性)。

filter(x -> x % 2 == 0, asList(1,2,3,4,5,6)); //filters even integers
filter(x -> x % 2 != 0, asList(1L,2L,3L,4L,5L,6L)); //filters odd longs
filter(x -> x >= 0.0, asList(-1.0, 1.0)); //filters positive doubles

根据Cardelli的说法,这是一种通用多态性。

强迫

double sum = 1 + 2.0;

整数和浮点算术是完全不同的。如果没有某种形式的强制,就不可能将加号运算符应用于此处不同类型的两个操作数。

在此示例中,整数和双精度类型自动强制(转换)为类型 double,而不进行显式强制转换。整个表达式将提升为双精度。之所以如此,是因为在Java中,我们有原始的加宽转换。

根据Cardelli的说法,这种形式的自动强制是为加运算符提供的一种临时多态性形式。

有些语言甚至不能在没有显式强制转换的情况下对整数和浮点数求和(即AFAIK,SML,顺便说一句,参数化多态性是克服此类问题的关键)。

超载

double sum = 2.0 + 3.0;
String text = "The sum is" + sum;

这里的加号运算符表示两种不同的东西,具体取决于所使用的参数。显然,操作员已超载。这意味着它具有不同的实现,具体取决于操作数的类型。根据Cardelli的说法,这是为加号算子提供的一种临时多态性形式。

当然,这也适用于类中的方法重载形式(即java.lang.Math方法min和max被重载以支持不同的基元类型)。

其他语言

即使继承在某些形式的多态性实现中起着重要作用,它肯定不是唯一的方法。其他不面向对象的语言提供其他形式的多态性。例如,在Python等动态语言中,甚至在像Go这样的静态类型语言中,或者在SML,Ocaml和Scala等语言中的代数数据类型中,或者在Haskell等语言中键入类型类,在Clojure中使用多方法,在JavaScript中进行原型继承等。


答案 2

无继承>运算符重载>的即席多态性

无继承>方法重载>的即席多态性

>具有继承性覆盖>的临时多态性方法

无继承>泛型>参数化多态性

子类型 多态性或包含 多态性 > 具有继承性的多态性赋值>

子类型 多态性或包含 多态性 多态性 > 具有继承性的多态性返回类型 >

子类型 多态性或包含 多态性 多态性 > 具有继承性的多态参数类型 >

强制 多态性 > 加宽> 有或没有继承

强制 多态性 > 自动装箱和取消装箱 > 无继承

无继承> Var args > 的强制多态性

无继承>型转换>的矫顽多态性