Java方法重载 + 双重分派

17

有人能详细解释一下,为什么在我的测试代码中使用 Child 实例时会调用重载方法 print(Parent parent) 的原因吗?

这里涉及到Java中虚拟方法或方法重载/解析的任何特殊之处吗? 是否有直接参考Java Lang Spec的内容? 哪个术语描述了这种行为? 非常感谢。

public class InheritancePlay {

    public static class Parent {        
        public void doJob(Worker worker) {
            System.out.println("this is " + this.getClass().getName());

            worker.print(this);
        }
    }

    public static class Child extends Parent {
    }

    public static class Worker {
        public void print(Parent parent) {
            System.out.println("Why this method resolution happens?");
        }

        public void print(Child child) {
            System.out.println("This is not called");
        }
    }

    public static void main(String[] args) {
        Child child = new Child();
        Worker worker = new Worker();

        child.doJob(worker);
    }
}
2个回答

24

JLS在§8.4.9 Overloading中规定:

  1. 当调用一个方法(§15.12)时,实参的数量(和任何显式类型参数)和实参的编译时类型在编译时被使用来确定将被调用的方法的签名(§15.12.2)。
  2. 如果要调用的方法是实例方法,则将在运行时使用动态方法查找(§15.12.4)来确定要调用的实际方法。

所以在你的情况下:

  1. 方法参数(this)的编译时类型为Parent,因此调用print(Parent)方法。
  2. 如果Worker类被子类化并且子类覆盖了该方法,并且worker实例是该子类的实例,则将调用覆盖的方法。

Java中不存在双重分派。您必须模拟它,例如使用Visitor Pattern。在这种模式中,每个子类实现一个accept方法并使用this作为参数调用访问者,并且this的编译时类型为该子类,因此使用所需的方法重载。


Christian,感谢您详尽的回答! 所以我们在这里处理运行时类型和编译时类型的问题。我会深入探讨这个话题。(提到双重分派是因为我在学习Visitor模式时遇到了这个问题 :))。Max - Max

5
原因在于`doJob`是在`Parent`中实现而不是在`Child`中重载。它将`this`传递给工人的`print`方法,因为`this`是类型为`Parent`的,所以将调用`Worker::print(Parent)`方法。
为了调用`Worker::print(Parent)`方法,需要在`Child`中重载`doJob`。
public static class Child extends Parent {
    public void doJob(Worker worker) {
        System.out.println("from Child: this is " + this.getClass().getName());

        worker.print(this);
    }
}

在上面的代码中,Child 中的 this.getClass() 等同于 Child.class

rsp,感谢您指出这一点,但我知道在Child中覆盖doJob可以使程序工作。问题是我不明白为什么会这样 :)。让我们看一下初始代码。当我们将Child对象传递给Parent.doJob时,Parent.doJob内部的Java反射证明我们正在处理类型为Child的this对象,那么为什么重载方法的解析失败呢? - Max
1
@Max,在一个方法内,this 的类型始终是该方法所在类的类型。编译器无法知道该类将来是否会被继承,因此必须使用它在那个时刻拥有的知识。this.getClass() 是运行时信息,而不是编译时信息。 - rsp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接