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



在下面的代码中,第一个和第二个print语句如何打印出SubObj??top和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、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是扩展了Object的String类型。所以自从1)call 'top。f'指的是Top类的f方法,2)在Top类中没有接受String参数的f方法,3)因为str是Object的子类,所以Top类的f方法是编译时唯一有效的选择。因此,编译器隐式地将str向上转换为它的父类型Object,这样它就可以传递给Top的f方法。(这与动态绑定相反,在动态绑定中,上述代码行的类型解析将延迟到运行时,由JVM而不是编译器解析。)

然后在运行时,在上面的代码行中,JVM将top向下转换为它的实际类型sub。然而,参数str已被编译器向上转换为Object类型。所以现在JVM必须调用类sub中的f方法,该方法接受Object类型的参数。

因此,上面这行代码输出的是" subbobj "而不是"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)
    }
}

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

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

然而,确切的方法签名是在编译时确定的(Java不支持多调度,单调度只在运行时基于接收对象而变化)——这解释了为什么Sub.f(String)结果为"Sub",例如,当Top.f(String)"绑定"到匹配Top.f(Object)的方法时,即使在Top的子类型上调用。(这是在编译时确定的最佳合格签名)。虚拟调度本身也是一样的。

幸福的编码。

这与对象的表面类型有关。在编译时,Java根据您声明的对象类型而不是实例化的特定类型进行类型检查。

你有一个类型Top和一个方法f(Object)。所以当你说:

 System.out.println(top.f(obj));

Java编译器只关心对象top是top类型,并且唯一可用的方法接受object作为参数。然后在运行时调用实际实例化对象的f(Object)方法。

下一个调用用同样的方式解释。

接下来的两个调用被解释为您所期望的。

是的,它们都指向Sub类。问题是top只知道

f(Object o)

,它只能调用那个签名

但是sub知道这两个签名,所以必须根据参数类型进行选择

在继承中,基类对象可以引用派生类的实例。

这就是Top top = sub;的工作原理。

  1. For System.out.println(top.f(obj));:

    top对象尝试使用Sub类的f()方法。现在在Sub类中有两个f()方法,对传递的参数进行类型检查。因为类型是Object,所以调用Sub类的第二个f()方法。

  2. For System.out.println(top.f(str));:

    你可以解释为(1),即类型是String,所以第一个f()函数被调用。

  3. For System.out.println(sub.f(obj));:

    这很简单,因为您正在调用Sub类本身的方法。现在,由于Sub类中有两个重载方法,这里也对传递的参数进行类型检查。由于传递的参数类型为Object,因此调用第二个f()方法。

  4. For System.out.println(sub.f(str));:

    类似于3。,这里传递的类型是String,所以Sub类的第一个f()函数被调用。

希望这对你有帮助。

in

Sub sub = new Sub();
Top top = sub;

创建了sub的实例,然后将其向上强制转换为top,这使得它只知道top中存在的方法。top中存在的方法是public String f(Object o) {return "Top";}

现在这个方法也被sub重载了,所以当你创建一个sub实例并将其上推到top时,它会被调用。

另一种说法是你得到

sub类型作为表面类型,但top作为实际类型,因为您将sub分配给了top。如果它重载了实际类型,你将调用表面类型中的方法,但是你不能调用实际类型

中不存在的任何方法。

相关内容

  • 没有找到相关文章

最新更新