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

Posted

技术标签:

【中文标题】没有继承就可以实现多态性吗?【英文标题】:Is polymorphism possible without inheritance? 【发布时间】:2012-07-28 18:22:40 【问题描述】:

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

【问题讨论】:

【参考方案1】:

我读过的关于这个主题的最佳解释是著名类型理论家Luca Cardelli 的一篇文章。文章名为On Understanding Types, Data Abstraction, and Polymorphism。

多态的类型

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

通用 参数化 包含 临时 重载 强制

与继承相关的多态性分为包含多态性或亚型多态性。

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

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

另一篇名为Polymorphism in object-oriented programming 的***文章似乎也回答了您的问题。

在 Java 中

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 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

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

强制

double sum = 1 + 2.0;

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

在此示例中,类型 integer 和 double 会自动强制(转换)为 double 类型,而无需显式强制转换。整个表达式提升为两倍。之所以如此,是因为在 Java 中我们有原始的扩展转换。

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

在某些语言中,如果没有显式强制转换,您甚至无法将整数和浮点数相加(即 AFAIK、SML,顺便说一下,参数多态性是克服此类问题的关键)。

重载

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

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

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

其他语言

即使继承在其中一些形式的多态性的实现中起着重要作用,当然它也不是唯一的方法。其他非面向对象的语言提供其他形式的多态性。例如,duck typing 在 Python 等动态语言甚至 Go 等静态类型语言中的案例,或在 SML、Ocaml 和 Scala 等语言中的 algebraic data types 或在 Haskell、@987654329 等语言中的 type classes 案例Clojure 中的 @,javascript 中的 prototypal inheritance 等

【讨论】:

反响很好。最好是从一个明确而清晰的摘要开始回答问题。 @RichardSitze 我试了一下以获得更详细的解释。我希望我没有掩盖原帖的意思。 @Edwin Delorzo 哇.. 实际上我试图建议在顶部使用单线来说明:是或否。然后读者在阅读时会知道他们要去哪里其余的部分。此外,正如 Stephen C 所指出的,答案可能与 Java 不同(这是最初的问题),然后是其他语言。干得好! 是的,还有其他语言,如 Ruby、Python、JavaScript、Visual Basic 和 Smalltalk,利用强大的 CPU 使用解释器将类型检查推迟到运行时(无论源代码是否编译为字节码或纯粹解释)。通过推迟类型检查,我们打破了继承和多态性之间的联系,【参考方案2】:

即席多态 > 运算符重载 > 没有继承

即席多态 > 方法重载 > 没有继承

即席多态 > 方法覆盖 > 继承

参数多态 > 泛型 > 没有继承

子类型多态或包含多态> 多态赋值> 带继承

子类型多态或包含多态> 多态返回类型> 带继承

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

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

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

强制多态 > Var args > 没有继承

强制多态 > 类型转换 > 没有继承

【讨论】:

【参考方案3】:

当然。在 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;
  

然后在别处:

Foo x = new A();
System.out.println(x.a())
Foo y = new B();
System.out.println(y.a())

xy都是Foos,但是调用a()时结果不同。

【讨论】:

接口继承。语义契约(从不强制)是隐式继承的。请参阅 Edwin 的回复。 好吧,这很公平。我认为提问者的意思是“实现继承”。在没有面试官进一步澄清的情况下,尤其是在面向 Java 的面试中,这个答案可能就是他们想要的。当然,还有更详细的答案。 是的,我同意。我倾向于将接口(正确或错误)视为向编译器保证对象将具有具有特定签名的特定方法的一种方式,这让我想到了鸭子类型,这并不意味着继承。从技术上讲,它被认为是继承,当然。 @Dhananjay 不,它没有。继承(扩展)复制行为;实现接口不会。【参考方案4】:

静态类型

重载 - 这意味着多个具有相同名称但签名不同的方法,可以不覆盖

class StaticPolyExample

void print(int s)

    //print s


void print(String s)

    //print s



动态类型

overriding - 这意味着超类中的方法将在需要继承的子类中重新定义

class Printer

void print(String s)

  // prints String




class diffPrinter extends Printer

void print(String s)

  // prints String differently



【讨论】:

【参考方案5】:

函数重载是一种多态性(尽管它不是真正的多态性),无需继承即可实现。

例如

class Foo  
public void Arrest( Animal A)
    /*code...*/
   
public void Arrest( Terrorist T ) 
    /*code...*/
   




from main :

Foo f= new Foo();
f.Arrest( new Lion() );
f.Arrest(new Terrorist());

arrest方法被调用了2次,但是代码的执行路径不同。

*这又不是真正的多态形式。 一般而言,真正的多态性如果没有继承是无法实现的。

【讨论】:

非面向对象的语言提供了不依赖于继承的多态形式(即parametric polymorphism)。在 Java 中,其中一些可以通过泛型实现。 @EdwinDalorzo :这些语言中多态性的定义是什么?它可能与 oop 中的不同。【参考方案6】:

是的,我想他们可能想听听接口的多态性。因此,如果有 2 个类从同一个接口实现,那么我们可以在我们期望具有这种接口的对象的所有地方使用。参见***的代码:

// from file Animal.java

public interface Animal 
        public String talk();


// from file Cat.java

public class Cat implements Animal 
        @Override
        public String talk() 
                return "Cat says Meow!";
        


// from file Dog.java

public class Dog implements Animal 
        @Override
        public String talk() 
                return "Dog says Woof! Woof!";
        


// from file PolymorphismExample.java

public class PolymorphismExample 

        public static void main(String[] args) 
                Collection<Animal> animals = new ArrayList<Animal>();
                animals.add(new Cat());
                animals.add(new Dog());

                for (Animal a : animals) 
                        System.out.println(a.talk());
                
        


【讨论】:

将 Max 的答案复制到 B 实现 goo 而不是 foo 的错误。

以上是关于没有继承就可以实现多态性吗?的主要内容,如果未能解决你的问题,请参考以下文章

多态的作用

多态实现之虚函数

多态你真的了解吗?

java继承和多态的学习

c++中,虚函数能不能被继承

浅谈 C# 多态的魅力 - 虚方法抽象接口实现