有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java静态和动态绑定、重载

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

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的对象传递给print,所以参数类型为Moe的对应print方法在Curly中运行。这个方法的输出是Curly 1,这是正确的答案

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

代码的正确输出为:

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

共 (4) 个答案

  1. # 1 楼答案

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

    传递Moe将调用print(Moe)。因为实际的类是Curly,所以它打印“Curly 1”

    传递一个Larry将调用print(Larry),因为这是一个更好的匹配print(Moe)。这将打印“卷曲2”

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

    现在试着找出剩下的

  2. # 2 楼答案

    这是一个非常令人困惑和可怕的例子,说明你永远不应该做的事情。所声明的变量类型对签名方法有什么影响。因此Larry没有接受Curly的方法,因此编译器将参数视为Larry。但是它被分派到Curly的方法版本

    所以是的,永远不要这样做

  3. # 3 楼答案

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

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

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

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

    • 动态绑定负责在编译时查找和调用静态绑定选择的方法的代码。它将尝试在变量持有的实际实例类型中查找方法代码。换句话说,如果你有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());
    
    • 静态绑定将尝试在类B中找到可以处理类型A的实例的最佳print方法签名。在这种情况下,它将是print(A)
    • 之后,动态绑定将在类C中搜索该方法的代码(因为这是由bc变量持有的实例类型),这意味着我们将看到C.print(A)

    类似地,在bc.print(new C());

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

    因此,它将调用C.print(B)

  4. # 4 楼答案

    下面是发生的事情:

    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
    

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