【问题标题】:Java inheritance vs polymorphism [duplicate]Java继承与多态[重复]
【发布时间】:2012-04-11 16:38:43
【问题描述】:

继承和多态都构成 IS-A 关系吗?继承和“覆盖”多态性是否发生在运行时,而“重载”多态性发生在编译时?我问这个的原因是因为很多论坛似乎给出了相互矛盾且经常令人困惑的答案。

谢谢!

【问题讨论】:

  • 我认为重载与多态无关。
  • 实际上这是多态的一种形式:来自维基百科:术语 ad-hoc 多态是指多态函数,可应用于不同类型的参数,但根据类型的不同表现不同应用它们的参数(也称为函数重载或运算符重载)..
  • @edalorzo:en.wikipedia.org/wiki/Ad-hoc_polymorphism 的事实准确性存在争议。
  • @Jayan 我认为它需要适当的参考,当然我不是该领域的专家,但是在 Google 图书中简单搜索“Ad hoc polymorphism”为重载存在提供了许多著名的参考被认为是多态特征。

标签: java inheritance polymorphism


【解决方案1】:

• 继承定义了两个类之间的父子关系,多态性利用这种关系在您的代码中添加动态行为。

• 继承通过允许子类从父类继承行为来鼓励代码可重用性。另一方面,多态性允许子类重新定义父类中已经定义的行为。如果没有多态性,子代无法在由 Parent 引用变量表示时执行自己的行为,但使用多态性可以做到。

• Java 不允许类的多重继承,但允许接口的多重继承,这实际上是实现多态所必需的。例如,一个类可以同时是 Runnable、Comparator 和 Serializable,因为这三个都是接口。这使它们在代码中传递,例如您可以将此类的实例传递给接受 Serializable 的方法,或传递给接受 Comparator 的 Collections.sort()。

• 多态性和继承都允许面向对象的程序发展。例如,通过使用继承,您可以在身份验证系统中定义新的用户类型,通过使用多态,您可以利用已经编写的身份验证代码。由于继承保证了最小的基类行为,依赖于超类或超接口的方法仍然可以接受基类的对象并对其进行身份验证。

【讨论】:

    【解决方案2】:

    多态性:不同对象接收相同消息并做出不同响应的能力。

    继承是实现它的一种方式,但不是必须的。见Duck Typing

    方法的重载是“编译时语法助手”——因为每个方法在编译后都会获得唯一的签名。与多态性无关。

    【讨论】:

      【解决方案3】:

      对于您问题的第一部分,我认为Wikipedia 提供了一个很好的定义:

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

      另一个名为Polymorphism in object-oriented programming 的维基百科文章似乎很好地回答了您的问题。本文中的第二个参考文献On Understanding Types, Data Abstraction, and Polymorphism 也非常详细地介绍了这个问题。

      Java 中的这种子类型化特性是通过类和接口的继承等方式实现的。尽管 Java 的子类型化特性在继承方面可能并不总是很明显。以泛型的协变和逆变的情况为例。此外,数组是可序列化和可克隆的,尽管这在类型层次结构中的任何地方都不明显。也可以说,通过原始扩展转换,Java 中的数值类型也是多态的。运算符的行为取决于其操作数。

      无论如何,继承在某些多态性的实现中起着重要作用。

      重载与覆盖

      您问题的第二部分似乎是关于选择给定方法的实现。显然,如果一个类重写了一个方法并且您创建了该类的一个实例,您希望调用该方法的重写版本,即使您通过父类的引用访问该对象也是如此。

      正确的实现方法的选择是在运行时完成的,正如您所指出的,现在要调用的方法的签名是在编译时决定的。由于重载是关于具有相同名称和不同签名的不同方法,这就是为什么说覆盖方法选择发生在编译时。

      在编译时覆盖方法选择

      15.12 节Method Invocation Expressions 中的Java Language Specification (JLS) 详细解释了编译器选择正确调用方法所遵循的过程。

      在那里,您会注意到这是一个编译时 任务。 JLS 在第 15.12.2 小节中说:

      此步骤使用方法的名称参数表达式的类型 找到既可访问又适用的方法 可能有不止一种这样的方法,在这种情况下选择最具体的一种。

      要验证它的编译时性质,您可以进行以下测试。

      声明一个这样的类并编译它。

      public class ChooseMethod {
         public void doSomething(Number n){
          System.out.println("Number");
         }
      }
      

      声明第二个类调用第一个类的方法并编译它。

      public class MethodChooser {
         public static void main(String[] args) {
          ChooseMethod m = new ChooseMethod();
          m.doSomething(10);
         }
      }
      

      如果您调用 main,输出会显示 Number

      现在,向ChooseMethod 类添加第二个更具体的重载 方法,然后重新编译它(但不要重新编译其他类)。

      public void doSomething(Integer i) {
       System.out.println("Integer");
      }
      

      如果再次运行main,输出仍然是Number

      基本上,因为它是在编译时决定的。如果你重新编译MethodChooser类(有main的那个),再次运行程序,输出将是Integer

      因此,如果您想强制选择重载方法之一,则参数的类型必须与编译时的参数类型相对应,而不仅仅是在运行时。

      在运行时覆盖方法选择

      同样,方法的签名是在编译时决定的,但实际的实现是在运行时决定的。

      声明一个这样的类并编译它。

      public class ChooseMethodA {
         public void doSomething(Number n){
          System.out.println("Number A");
         }
      }
      

      然后声明第二个扩展类并编译:

      public class ChooseMethodB extends ChooseMethodA {  }
      

      在 MethodChooser 类中,您可以这样做:

      public class MethodChooser {
          public static void main(String[] args) {
              ChooseMethodA m = new ChooseMethodB();
              m.doSomething(10);
          }
      }
      

      如果你运行它,你会得到输出Number A,这没关系,因为ChooseMethodB中的方法没有被覆盖,因此被调用的实现是ChooseMethodA的实现。

      现在,在MethodChooserB 中添加一个覆盖方法:

      public void doSomething(Number n){
          System.out.println("Number B");
      }
      

      只重新编译这个,然后再次运行 main 方法。

      现在,您将获得输出 Number B

      因此,实现是在运行时选择的,不需要重新编译 MethodChooser 类。

      【讨论】:

      • 我认为 Jayan 的回答非常简洁。
      【解决方案4】:

      我认为你是对的。

      多态性考虑对象的运行时类型来决定执行哪个方法,选择调用哪个重载方法在运行时不是动态决定的,它取决于编译时参数的类型。

      【讨论】:

        【解决方案5】:

        只有继承构成 IS-A 关系。多态与它无关。

        “重载”是多态的例子。您可以了解更多关于运行时和编译时多态性here

        【讨论】:

          【解决方案6】:

          多态是继承的结果。它只能发生在相互扩展的类中。

          多态性确实发生在运行时;我从未听说过“重载多态性”。

          继承发生在编译时,即您编写的那一刻:

          class A extends B
          {
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-07-30
            • 2013-10-24
            • 2012-12-23
            • 2015-11-11
            • 2014-03-16
            • 1970-01-01
            • 1970-01-01
            • 2013-03-28
            相关资源
            最近更新 更多