18

在下面的代码中,第一个和第二个打印语句如何打印出 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
4

6 回答 6

15

既然你已经了解了案例 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) 调用 'top.f' 指的是 Top 类的 f 方法,2) Top 类中没有 f 方法接受 String 参数,以及 3) 因为 str 是 Object 的子类,所以 Top 类的 f 方法是编译时唯一有效的选择。所以编译器隐式地将 str 向上转型为它的父类型 Object,因此它可以传递给 Top 的 f 方法。(这与动态绑定形成对比,其中上述代码行的类型解析将推迟到运行时,由 JVM 而不是编译器解析。)

然后在运行时,在上面的代码行中,top 被 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)
    }
}
于 2011-04-25T00:45:24.857 回答
8

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

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

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

快乐编码。

于 2011-04-14T04:37:43.590 回答
2

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

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

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

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

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

接下来的两个调用将按照您的预期进行解释。

于 2011-04-14T04:39:25.010 回答
1

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

这就是Top top = sub;运作良好的方式。

  1. 对于System.out.println(top.f(obj));

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

  2. 对于System.out.println(top.f(str));

    您可以解释为与 (1) 相同,即类型为调用String第一个函数。f()

  3. 对于System.out.println(sub.f(obj));

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

  4. 对于System.out.println(sub.f(str));

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

希望这可以帮助。

于 2011-04-14T04:46:32.847 回答
1

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

f(Object o)

它只能调用那个签名。

但是sub知道两个签名,并且必须按参数类型进行选择。

于 2011-04-14T04:36:09.627 回答
0

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

你创建了一个 sub 的实例,然后将它转换为 top,这使得它只知道存在于 top 中的方法。存在于顶部的方法是公开的String f(Object o) {return "Top";}

现在该方法也被 sub 重载了,因此当您创建 sub 的实例并将其向上转换到顶部时,它将被调用。

另一种说法是你得到了

sub 类型作为表观类型,但 top 作为实际类型,因为您将 sub 分配给 top。如果它重载实际类型,您将调用表观类型中的方法,但您将无法调用实际类型中不存在的任何方法

于 2011-04-14T04:39:52.000 回答