没有继承可以实现多态吗?

Jus*_*per 51 java oop polymorphism inheritance

在一次采访中,我被问到是否可以在没有继承的情况下实现多态性.这可能吗?

Edw*_*rzo 66

我读过的关于这个主题的最佳解释是Luca Cardelli的一篇文章,他是一位着名的理论家.该文章名为On Understanding Types,Data Abstraction和Polymorphism.

多态性的类型

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

  • 普遍
    • 参数
    • 包容
  • 特设
    • 超载
    • 强迫

与继承相关的多态性类别被分类为包含多态性或亚型多态性.

Wikipedia提供了一个很好的定义:

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

在面向对象编程中另一篇名为Polymorphism的维基百科文章似乎也回答了你的问题.

在Java中

除了其他方法之外,Java中的这种子类型功能通过类和接口的继承来实现.尽管Java的子类型特征在继承方面可能并不明显.例如,协方差和与泛型相反的情况.此外,数组是Serializable和Cloneable,尽管在类型层次结构中的任何位置都不明显.也可以说通过原始扩展转换,Java中的数字运算符是多态的,在某些情况下甚至接受完全不相关的操作数(即字符串和数字或字符串加上一些其他对象的串联).还要考虑基元的装箱和拆箱的情况.后面这些多态性(强制和重载)的情况与继承完全无关.

例子

入选

List<Integer> myInts = new ArrayList<Integer>();
Run Code Online (Sandbox Code Playgroud)

这是您的问题似乎引用的情况,即类型之间存在继承或实现关系时,就像ArrayList实现List的情况一样.

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

List<? super Number> myObjs = new ArrayList<Object>();
List<? extends Number> myNumbers = new LinkedList<Integer>();
Run Code Online (Sandbox Code Playgroud)

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

Cloneable clone = new int[10];
Serializable obj = new Object[10]
Run Code Online (Sandbox Code Playgroud)

即便如此,根据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;
  }
}
Run Code Online (Sandbox Code Playgroud)

可以使用相同的算法来过滤具有各种谓词的所有类型的列表,而不必为每种可能类型的列表重复单行代码.实际列表的类型和谓词的类型是参数化的.使用JDK 8 Preview中可用的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
Run Code Online (Sandbox Code Playgroud)

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

强迫

double sum = 1 + 2.0;
Run Code Online (Sandbox Code Playgroud)

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

在此示例中,integer和double类型会自动强制(转换)为double类型而不进行显式强制转换.整个表达式被提升为双倍.这是因为在Java中我们有原始的扩展转换.

根据Cardelli的说法,这种形式的自动强制是为plus运算符提供的一种ad-hoc多态.

有些语言甚至无法对没有显式转换的整数和浮点数求和(即AFAIK,SML,顺便说一句,参数多态是解决此类问题的关键).

超载

double sum = 2.0 + 3.0;
String text = "The sum is" + sum;
Run Code Online (Sandbox Code Playgroud)

这里的plus运算符意味着两种不同的东西,具体取决于使用的参数 显然,操作员已经超负荷运转.这意味着它具有不同的实现,具体取决于操作数的类型.根据Cardelli的说法,这是为plus运算符提供的一种ad-hoc多态.

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

在其他语言

即使继承在实现某些形式的多态中起着重要作用,当然它也不是唯一的方法.其他非面向对象的语言提供其他形式的多态性.举个例子来说,案件鸭打字像Python或即使是在像围棋,或静态类型语言的动态语言的代数数据类型,像SML,ocaml的和Scala,或语言类型类的象Haskell语言,多方法用Clojure ,JavaScript中的原型继承

  • 很好的反应。最好是你用一个明确而清晰的总结来回答这个问题。 (2认同)
  • @Edwin Delorzo哇..实际上,我试图建议在顶部说一个单行:是或否.然后读者会知道他们在阅读其余部分时会去哪里.此外,正如斯蒂芬C指出的那样,答案可能是不同的Java(这是原始问题),然后是其他语言.不错的工作! (2认同)

小智 6

Ad-hoc多态>运算符重载>无继承

Ad-hoc多态>方法重载>无继承

Ad-hoc多态>方法覆盖>具有继承性

参数多态>泛型>无遗传

亚型多态性或包含多态性>多态分配>具有遗传性

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

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

强制多态>扩展>有或没有继承

强制多态>自动装箱和拆箱>没有继承

强制多态> Var args>无遗传

强制多态>类型铸造>无遗传


Max*_*Max 5

当然.在Java中,您可以让两个类实现相同的接口,并且它们的结果是多态的.没有继承任何功能.

public interface Foo {
  public int a();
}

public class A implements Foo {
  public int a() {
    return 5;
  }
}


public class B implements Foo {
  public int a() {
    return 6;
  }
}
Run Code Online (Sandbox Code Playgroud)

其他地方:

Foo x = new A();
System.out.println(x.a())
Foo y = new B();
System.out.println(y.a())
Run Code Online (Sandbox Code Playgroud)

这两个xyFooS中,但有不同的结果,当你打电话a().

  • 接口是继承的.语义契约(永不强制)是隐式继承的.请参阅Edwin的回复. (5认同)