关于 Java 重载和动态绑定的问题

2022-09-02 21:07:38

在下面的代码中,第一个和第二个打印语句如何打印出 SubObj??top 和 sub 是否指向同一个 Sub 类?

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";}
    public String f(Object o) {return "SubObj";}
}

public class Test {
    public static void main(String[] args) {  
        Sub sub = new Sub();
        Top top = sub;
        String str = "Something";
        Object obj = str;


        System.out.println(top.f(obj));
        System.out.println(top.f(str));
        System.out.println(sub.f(obj));
        System.out.println(sub.f(str));
    }
}

上面的代码返回下面的结果。

SubObj
SubObj
SubObj
Sub

答案 1

由于您已经了解了案例 1、3 和 4,因此让我们处理案例 2。

(请注意 - 我绝不是JVM或编译器内部工作的专家,但这就是我的理解。如果阅读本文的人是JVM专家,请随时编辑您可能发现的任何差异的答案。

子类中名称相同但签名不同的方法称为方法重载。方法重载使用静态绑定,这基本上意味着在编译时将强制“选择”(即绑定)适当的方法。编译器对对象的运行时类型(也称为实际类型)一无所知。所以当你写:

                         // Reference Type  // Actual Type
    Sub sub = new Sub(); // Sub                Sub
    Top top = sub;       // Top                Sub

编译器只“知道”top 是 Top 类型(也称为引用类型)。所以当你后来写:

    System.out.println(top.f(str)); // Prints "subobj"

编译器“看到”调用'top.f'指的是Top类的f方法。它“知道”str是扩展对象的字符串类型。因此,由于1)调用“top.f”指的是Top类的f方法,2)类Top中没有采用String参数的f方法,3)由于str是Object的子类,因此Top类的f方法是编译时唯一有效的选择。因此,编译器将 str 隐式上放到其父类型 Object,以便可以将其传递给 Top 的 f 方法。(这与动态绑定相反,动态绑定将上述代码行的类型解析推迟到运行时,由 JVM 而不是编译器解析。

然后在运行时,在上面的代码行中,顶部被JVM向下转换为它的实际类型,sub。但是,参数 str 已被编译器上调以键入 Object。所以现在JVM必须在类sub中调用一个f方法,该方法采用Object类型的参数。

因此,上面的代码行打印“subobj”而不是“sub”。

有关另一个非常相似的示例,请参阅:Java 动态绑定和方法重写

更新:找到这篇关于JVM内部工作原理的详细文章:

http://www.artima.com/underthehood/invocationP.html

我注释了您的代码,以使其更清楚地了解发生了什么:

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";} // Overloading = No dynamic binding
    public String f(Object o) {return "SubObj";} // Overriding = Dynamic binding
}

public class Test {
    public static void main(String[] args) {  

                                  // Reference Type     Actual Type
        Sub sub = new Sub();      // Sub                Sub
        Top top = sub;            // Top                Sub
        String str = "Something"; // String             String
        Object obj = str;         // Object             String

                                        // At Compile-Time:      At Run-Time:
        // Dynamic Binding
        System.out.println(top.f(obj)); // Top.f (Object)   -->  Sub.f (Object)

        // Dynamic Binding
        System.out.println(top.f(str)); // Top.f (Object)   -->  Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(obj)); // Sub.f (Object)        Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(str)); // Sub.f (String)        Sub.f (String)
    }
}

答案 2

这是因为 Java 中的所有方法调用都是虚拟(缺省情况下)。

也就是说,解析从实际对象(不是表达式类型)开始,并“处理”继承链(根据实际对象类型),直到找到第一个匹配的方法。非虚拟方法将从表达式类型开始。(将方法标记为非虚拟方法。final

但是,确切的方法签名是在编译时确定的(Java不支持多调度,单调度仅在运行时根据接收方对象而变化) - 这解释了为什么导致“Sub”,例如,即使对Top的子类型调用,也会“绑定”到方法匹配。(这是在编译时确定的最佳合格签名)。虚拟调度本身是一样的。Sub.f(String)Top.f(String)Top.f(Object)

快乐编码。