Java - 调用方法时的继承和变量类型

Java - inheritance and type of variables when invoking a method

我在理解某些代码的结果时遇到了一些大问题。

public class ClassA {
    public void stampa(ClassA p) {
        System.out.println("AAA");
    }
}
public class ClassB extends ClassA {
    public void stampa(ClassB p) {
        System.out.println("BBB");
    }
    public void stampa(ClassA p) {
        System.out.println("AAA/BBB");
    }
}
public class ClassC extends ClassA {
    public void stampa(ClassC p) {
        System.out.println("CCC");
    }
    public void stampa(ClassA p) {
        System.out.println("AAA/CCC");
    }
}

主要是这样的

public static void main(String[] args) {
    ClassA a1, a2;
    ClassB b1;
    ClassC c1;
    a1 = new ClassB();
    b1 = new ClassB();
    c1 = new ClassC();
    a2 = new ClassC();
    b1.stampa(b1);//BBB
    a1.stampa(b1);//AAA/BBB
    b1.stampa(c1);//AAA/BBB
    c1.stampa(c1);//CCC
    c1.stampa(a1);//AAA/CCC
    a2.stampa(c1);//AAA/CCC
}

我很难理解 a1.stampa(b1) 的结果为何;是 "AAA/BBB" 而不是 "BBB"。 正如我从继承中了解到的,编译时a1的静态类型是ClassB,所以我在ClassB中搜索"stampa"方法,对于方法的参数,静态类型也是ClassB,所以我会选择ClassB 的第一个 stampa 方法。

当我试图理解 a2.stampa(c1) 的结果时,同样的事情也会发生;这是 "AAA/CCC" 而不是 "CCC".

有人可以帮助我理解我做错了什么吗?

问题是以下

public class ClassB extends ClassA {
    public void stampa(ClassB p) {

不是覆盖这个

public class ClassA {
    public void stampa(ClassA p) {

这是超载它...

这意味着它是一个额外的方法,而不是覆盖,并且只能通过 b1.stampa(b1);.

访问

如果您具有相同的方法签名,则仅 发生 覆盖,但称为 'co-variant returns' 的东西除外,本质上是相同的概念,但类型为 return是一个子类。

这是一个与重载和多态相关的问题。

ClassA a1;
ClassB b1;
...
a1.stampa(b1);

编译器只能确定编译时调用的两个参数类型和函数名(因为多态性)

所以"a1.stampa(b1);"让编译在ClassA中查找参数类型为ClassB的戳记,但是ClassA没有此功能,所以参数被降级,编译在ClassA中查找参数类型为ClassA的戳记。这个函数是存在的,所以编译器在这一行记录下这里应该调用一个名字为stampa,参数类型为ClassA的函数。

当程序运行时,a1实际上是一个ClassB对象,这个对象覆盖了名称为stampa、参数类型为ClassA的函数。因此,最终输出为"AAA/BBB".

记住重载发生在编译时,多态性发生在执行时。

请忽略我蹩脚的英文

关于覆盖重载方法的区别

方法有一个 signature,由方法名称和参数类型定义,但不是参数名称或 return 类型。

您可以在 class 上定义多个具有相同名称的方法,只要签名不同即可。这叫做重载.

如果 subclass 定义了一个与 superclass 具有 相同 签名的方法,则 subclass 是 覆盖 superclass 方法。这意味着将调用 subclass 方法,而不管用于调用的变量类型如何。

Java 编译器确定 在编译时调用哪个 方法,即要使用的方法签名。要调用的具有该签名的实际方法在 运行-time 时确定,具体取决于引用对象的实际 class。

因此,在 编译时 ,对于 a1.stampa(b1),编译器会查看 class ClassA 上的所有 stampa (因为 a1ClassA 类型),并且只找到一个这样的方法,签名为 stampa(ClassA).

运行时a1引用的对象实际上是一个ClassB,所以ClassB.stampa(ClassA)是已调用,因为 ClassB 已覆盖 ClassA 中的方法。

ClassB.stampa(ClassA) 然后打印 AAA/BBB.

据我所知 a1.stampa(b1) 的结果;是 "AAA/BBB" 而不是 "BBB" 因为当您将 b1 传递给 stampa() 时,它将被类型转换为超类引用并且因为您有超类的重载方法并且在执行重载方法时总是子类方法获得优先权所以执行子类方法而不是超类方法。

这是运行时多态性,它是通过覆盖方法实现的。 首先,你甚至不能指望 a1.stampa(b1); 调用一个不覆盖父 class 方法的方法(在你的情况下恰好是 public void stampa(ClassB) )。这个方法永远不会被调用来自您的调用,因为它没有覆盖父 class 方法。

现在调用 public void stampa(ClassA p) 的原因是因为在运行时父 class 引用变量 a1 引用 ClassB 的对象。所以,方法调用覆盖父 class 方法的方法。

你的其他问题也是如此。

为了更好地理解运行时多态性,您可以参考https://docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html