【问题标题】:compile-time and run-time binding in method overloading and overriding方法重载和覆盖中的编译时和运行时绑定
【发布时间】:2014-12-09 13:58:01
【问题描述】:

据我了解:

类的私有、最终和静态方法遵循编译时绑定,即在编译时决定调用哪个方法。

但是,对非私有实例方法的调用是在运行时决定的。

到目前为止,这曾经解决了我所有的问题。但是,我遇到了一些问题,即上述语句没有得到正确的输出。

代码如下:

class Item
{
    Integer size;
    Item(Integer size)
    {
      this.size=size;
    }
    public boolean equals(Item item2) //not overriding the method of Object class
    {
        if(this==item2)
        return true;
        return this.size.equals(item2.size);
     }

  }

public class Test 
{
    public static void main(String[] args)
     {
        Item itemA= new Item(10);
        Item itemB= new Item(10);
        Object itemC=itemA;
        System.out.println("|"+ itemA.equals(itemB) + "|" + itemC.equals(itemB)+ "|");
     }

}

它给出的输出:|true|false|

我期望的输出:|true|true|

这里,class Itemequals 方法没有覆盖Objectequals 方法,但是由于equals 方法的方法签名不同,正在发生重载。

问题:在调用itemC.equals(itemB)时,为什么会调用Object类的equals方法。

据我说:itemC 在运行时拥有class Item 的对象,因此,应该调用Item classequals。在运行时,Item class 中有两个equals 方法,一个是它自己的,另一个是从Object 类继承的。所以,应该调用equals(Item) 而不是equals(Object),因为调用的是equals(Item)

我在概念上到底缺少什么?

【问题讨论】:

  • 如果您的期望是正确的,那么 Java 将是一种多分派语言,并且许多事情会更容易:) 另一方面,那么(多)方法在逻辑上将不再属于单课。

标签: java overloading overriding


【解决方案1】:

重载是在编译时确定的,而不是运行时。所以itemC.equals(itemB)调用Objectequals方法,因为itemC变量的类型是Object

【讨论】:

    【解决方案2】:

    来自jls reg overloading 的片段

    When a method is invoked (§15.12), the number of actual arguments (and any explicit type
    arguments) and the compile-time types of the arguments are used, at compile time, to determine the 
    signature of the method that will be invoked (§15.12.2). If the method that is to be invoked is an 
    instance method, the actual method to be invoked will be determined at run time, using dynamic method 
    lookup (§15.12.4). 
    

    基于上面的语句,itemC.equals(itemB) 调用了Object 类的equals 方法,因为在编译时类型itemCObject

    【讨论】:

      【解决方案3】:

      问题:在调用itemC.equals(itemB)时,为什么会调用Object类的equals方法。

      因为itemC的编译时类型是Object

      重写是在执行时根据目标对象的实际类型进行的,而重载是在编译时根据目标的编译时类型进行的表达。

      如果您使用javap -c -v Test,您将看到涉及的两个方法调用:

      常量池:

      ...
      #6 = Methodref #2.#32    //  Item.equals:(LItem;)Z
      #7 = Methodref #16.#33   //  java/lang/Object.equals:(Ljava/lang/Object;)Z
      ...
      

      然后是main 方法:

      33: invokevirtual #6  // Method Item.equals:(LItem;)Z
      ...
      40: invokevirtual #7  // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
      

      这样就显示了正在调用的方法的签名。执行该签名的哪个实现取决于执行时类型。因此,如果您在 Item 中覆盖 equals(Object),那么将为 itemC.equals(itemA) 调用该覆盖。

      【讨论】:

      • 那么你是说在编译时,调用itemC.equals(itemB) 被解析并绑定到Object 类的equals 方法。在编译时,itemCObject 类型,它不知道它将在运行时存储哪个对象,那么 itemC.equals 是如何在此处发生重载的
      • @VikasMangal:重载 only 使用编译时信息。 itemC 的类型为ObjectitemA 的类型是 Item,因此编译器会寻找合适的 equals 方法。 equals(Object) 是这里唯一的可能性(有一个从 ItemObject 的隐式转换,所以方法签名是有效的),所以这就是被使用的。
      • 可以编译Item并测试运行。然后将 Item.equals() 更改为具有 Object 参数,仅编译 Item 并运行。并将参数类再次更改回 Item,只编译 Item 并运行。编译器在Test的编译时决定了什么??
      • @laune:我不确定你的意思——你只是在改变Item,所以不可能改变Test中的代码......你看到了什么行为让你吃惊吗?
      • @laune 编译器选择在编译时为 itemC.equals(itemB) 调用 equals(Object)。如果此方法稍后被 Item 覆盖(如果您进行了建议的更改),则在运行时将调用 Item 的 equals(Object) 而不是 Object 的 equals(Object)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-08
      相关资源
      最近更新 更多