Java继承与多态

标签 java inheritance polymorphism

这个问题在这里已经有了答案:





What is the main difference between Inheritance and Polymorphism?

(18 个回答)


4年前关闭。




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

谢谢!

最佳答案

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

In object-oriented programming, subtype polymorphism or inclusion polymorphism is a concept in type theory wherein a name may denote instances of many different classes as long as they are related by some common super class. Inclusion polymorphism is generally supported through subtyping, i.e., objects of different types are entirely substitutable for objects of another type (their base type(s)) and thus can be handled via a common interface. Alternatively, inclusion polymorphism may be achieved through type coercion, also known as type casting.



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

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

无论如何,继承在某些这种多态性的实现中扮演着重要的角色。

重载与覆盖

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

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

在编译时覆盖方法选择

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

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

This step uses the name of the method and the types of the argument expressions to locate methods that are both accessible and applicable There may be more than one such method, in which case the most specific one is chosen.



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

声明一个这样的类并编译它。
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 class(有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课是必需的。

关于Java继承与多态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9901552/

相关文章:

java - 测试驱动开发、单元测试

java - Mockito:模拟方法抛出异常

java - 在 Nashorn 中禁用 ECMAScript 扩展

Java:方法的继承

c# - 使用派生泛型派生类的实例调用方法

ruby-on-rails - Rails 4:无法更新多态对象

java - android 无法添加Fragment

c++ - 用部分模板特化覆盖抽象成员函数

css - 我该如何解决这个 CSS 链接继承问题?

c++ - 为什么我不能在派生类重写函数中调用 protected 虚拟基类函数?