【问题标题】:Java Static and Dynamic Binding, OverloadingJava 静态和动态绑定、重载
【发布时间】:2019-01-19 01:15:46
【问题描述】:

我正在练习一个测试,我遇到了这个关于重载和静态和动态绑定的练习。询问以下代码的输出:

class Moe {
    public void print(Moe p) {
        System.out.println("Moe 1");
    }
}

class Larry extends Moe {
    public void print(Moe p) {
        System.out.println("Larry 1");
    }
    public void print(Larry l) {
        System.out.println("Larry 2");
    }
}

class Curly extends Larry {
    public void print(Moe p) {
        System.out.println("Curly 1");
    }
    public void print(Larry l) {
        System.out.println("Curly 2");
    }
    public void print(Curly b) {
        System.out.println("Curly 3");
    }
}

class Overloading {
    public static void main (String [] args) {
        Larry stooge1 = new Curly();
        Moe stooge2 = new Larry();
        Moe stooge3 = new Curly();
        Curly stooge4 = new Curly();
        Larry stooge5 = new Larry();

        stooge1.print(new Moe());
        stooge1.print(new Curly());
        stooge1.print(new Larry());
        stooge2.print(new Curly());
        stooge3.print(new Curly());
        stooge3.print(new Larry());
        stooge5.print(new Curly());
    }
}

认为我得到了第一个,但在其他的我完全迷失了。这就是我解决第一个问题的方法:

在运行时stooge1 的类型是Curly,所以我们调用Curly 的print 方法。因为我们将Moe 类型的对象传递给打印,所以在Curly 中运行参数类型为Moe 的相应打印方法。这个方法的输出是Curly 1,正确答案。

但是,当我将这种技术应用于以下行时,我最终得到了错误的答案。有人能解释一下这个概念在 Java 中是如何工作的吗?

代码的正确输出是:

Curly 1
Curly 2
Curly 2
Larry 1
Curly 1
Curly 1
Larry 2

【问题讨论】:

  • 您使用 Moe、Larry 和 Curly 而不是 A、B 和 C 的事实令人难以置信。
  • Larry extends Moe你说的是Larry is a type of Moe我完全迷失了 - 我也是。

标签: java


【解决方案1】:

静态绑定发生在编译时,动态绑定发生在运行时。

  • 静态绑定负责选择应该执行的方法的签名(名称和参数类型)。它使用

    • 方法的名称
    • 持有参数的变量类型(编译器不会假设实际的对象变量在运行时会持有什么,他会选择能够处理所有可能情况的签名)。

    编译器选择签名从调用方法的变量类型,所以Object o = "abc";不允许你调用o.substring(1,2);,因为编译器将无法在Object 类中找到substring(int, int) 签名(这是调用substring 方法的o 变量的类型)。

  • 动态绑定负责在编译时查找和调用静态绑定选择的方法的代码。它将尝试在变量持有的实际实例类型中查找方法代码。换句话说,如果你有Animal a = new Cat(); a.makeSound();,你可以期望得到"Mew"的结果,因为在运行时JVM将从Cat类开始搜索和调用makeSound的代码。如果该类中不提供实现,JVM 将在祖先中搜索它,直到找到它的继承源。


我在您的示例中稍微重命名了类和变量,希望使其更具可读性:

class A {
    public void print(A a) {
        System.out.println("A.print(A)");
    }
}

class B extends A {
    public void print(A a) {
        System.out.println("B.print(A)");
    }
    public void print(B b) {
        System.out.println("B.print(B)");
    }
}

class C extends B {
    public void print(A a) {
        System.out.println("C.print(A)");
    }
    public void print(B b) {
        System.out.println("C.print(B)");
    }
    public void print(C c) {
        System.out.println("C.print(C)");
    }
}

class OverloadingDemo {
    public static void main (String [] args) {
        A ab = new B();
        A ac = new C();
        B bb = new B();
        B bc = new C();

        bc.print(new A());
        bc.print(new C());
        bc.print(new B());
        ab.print(new C());
        ac.print(new C());
        ac.print(new B());
        bb.print(new C());
    }
}

(变量命名-> 类型为X 的变量持有Y 类型的实例被命名为xy)。

所以,当我们执行时

bc.print(new A());
  • 静态绑定将尝试找到最好的print 方法签名在类B 中可用,它可以处理A 类型的实例。在这种情况下,它将是print(A)
  • 之后,动态绑定将在C 类中搜索此方法的代码(因为这是bc 变量持有的实例类型),这意味着我们将看到C.print(A)

bc.print(new C()); 的情况类似

  • 静态绑定将尝试为B 类中可用的C 参数找到最佳print 方法,对于Cprint(B)(因为那里没有print(C) 而B 是最接近的超类型) .
  • 所以现在动态绑定知道在C 类中查找哪个方法(因为这是bc 持有的实例)。

所以它会调用C.print(B)

【讨论】:

    【解决方案2】:

    这是怎么回事:

    stooge1.print(new Moe()); // All three have overload for Moe,
    // so the overload from the dynamic type of stooge1 gets called
    stooge1.print(new Curly()); // Compiler thinks stooge1 is Larry,
    // so it does not know that it has an overload for Curly.
    // It uses the overload for Larry instead, because Curly is a Larry
    stooge1.print(new Larry()); // Same logic as above applies.
    stooge2.print(new Curly()); // Compiler thinks stooge2 is Moe, so its only overload
    // is for Moe. Since the dynamic type is Larry, first overload is invoked
    

    其余三种情况可以通过应用与上述相同的逻辑来解决。

    【讨论】:

      【解决方案3】:

      对于#1、#2、#3 stooge1 被声明为Larry,因此只能调用Larry 可用的方法。

      传递Moe 将调用print(Moe)。由于实际的类是Curly,因此它会打印“Curly 1”。

      传递Larry 将调用print(Larry),因为这比print(Moe) 更匹配。这将打印“Curly 2”。

      传递Curly 也会调用print(Larry)。请注意print(Curly)stooge1 来说是未知的,因此编译器无法选择它。因此它也会打印“Curly 2”。

      现在试着找出其余的。

      【讨论】:

        【解决方案4】:

        所以这是一个非常令人困惑和可怕的例子,说明你不应该做的事情。变量的声明类型对于签名方法具有什么意义重大。所以Larry 没有接受Curly 的方法,所以编译器认为参数是Larry。但它会被分派到Curly 的方法版本。

        是的,永远不要这样做 =\

        【讨论】:

        • 从来不做哪个部分?我同意不使用这些名称,但是在子类中为更具体的参数类型重载是很常见的。例如。 java.text.Formatformat(Object),子类java.text.DateFormat 增加了重载format(Date),而DateObject 的子类。如果您创建一个DateFormat 并将其提供给采用Format 的代码并且该代码使用Date 参数调用format(),它将调用format(Object)正是这个问题在做什么。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多