【问题标题】:Java dynamic binding and method overridingJava 动态绑定和方法覆盖
【发布时间】:2008-11-26 19:26:21
【问题描述】:

昨天我有一个两个小时的技术电话面试(我通过了,哇哦!),但我完全忽略了以下关于 Java 中的动态绑定的问题。更令人费解的是,几年前我还是一名助教时曾向本科生教授这个概念,所以我给他们错误信息的前景有点令人不安......

这是我遇到的问题:

/* What is the output of the following program? */

public class Test {

  public boolean equals( Test other ) {
    System.out.println( "Inside of Test.equals" );
    return false;
  }

  public static void main( String [] args ) {
    Object t1 = new Test();
    Object t2 = new Test();
    Test t3 = new Test();
    Object o1 = new Object();

    int count = 0;
    System.out.println( count++ );// prints 0
    t1.equals( t2 ) ;
    System.out.println( count++ );// prints 1
    t1.equals( t3 );
    System.out.println( count++ );// prints 2
    t3.equals( o1 );
    System.out.println( count++ );// prints 3
    t3.equals(t3);
    System.out.println( count++ );// prints 4
    t3.equals(t2);
  }
}

我断言输出应该是来自被覆盖的equals() 方法中的两个单独的打印语句:t1.equals(t3)t3.equals(t3)。后一种情况很明显,前一种情况下,即使t1有一个Object类型的引用,它也被实例化为Test类型,所以动态绑定应该调用方法的覆盖形式。

显然不是。我的面试官鼓励我自己运行程序,你瞧,被覆盖的方法只有一个输出:t3.equals(t3) 行。

那么我的问题是,为什么?正如我已经提到的,即使 t1 是 Object 类型的引用(因此静态绑定会调用 Object 的 equals() 方法),动态绑定应该负责调用最具体的方法版本基于引用的实例化类型。我错过了什么?

【问题讨论】:

  • 请在此答案中找到我的帖子,我已尽力解释其他案例。我非常感谢您的意见:)

标签: java inheritance dynamic-binding


【解决方案1】:

Java 对重载方法使用静态绑定,对重载方法使用动态绑定。在您的示例中,equals 方法被重载(具有与 Object.equals() 不同的参数类型),因此调用的方法在编译时绑定到 reference 类型。

一些讨论here

它是equals方法这一事实并不真正相关,除了重载而不是覆盖它是一个常见的错误,根据你在面试中对问题的回答,你已经意识到了这一点。

编辑: 一个很好的描述here。此示例显示了与参数类型相关的类似问题,但由同一问题引起。

我相信如果绑定实际上是动态的,那么调用者和参数是 Test 实例的任何情况都会导致调用被覆盖的方法。所以 t3.equals(o1) 将是唯一不会打印的情况。

【讨论】:

  • 很多人指出它已重载而不是被覆盖,但即使这样,您仍希望它能够正确解决重载问题。据我所知,您的帖子实际上是迄今为止唯一正确回答问题的帖子。
  • 我的错误是完全忽略了该方法确实被重载而不是被覆盖的事实。我看到“equals()”并立即想到继承和覆盖。看起来我,再一次,正确地理解了更广泛和更困难的概念,但把简单的细节搞砸了。 :P
  • @Override 注释存在的另一个原因。
  • 跟着我重复:“Java 对重载方法使用静态绑定,对重载方法使用动态绑定”-+1
  • 所以我毕业时并不知道这一点。谢谢!
【解决方案2】:

Testequals 方法不会覆盖java.lang.Objectequals 方法。看参数类型! Test 类使用接受Test 的方法重载equals

如果equals 方法打算覆盖,它应该使用@Override 注释。这会导致编译错误指出这个常见的错误。

【讨论】:

  • 是的,我不太清楚为什么我错过了这个简单但至关重要的细节,但这正是我的问题所在。谢谢!
  • +1 是对提问者好奇结果的真实答案
  • 请在此答案中找到我的帖子,我已尽力解释其他案例。我非常感谢您的意见:)
【解决方案3】:

有趣的是,在 Groovy 代码(可以编译为类文件)中,除了一个调用之外,所有调用都会执行 print 语句。 (将 Test 与 Object 进行比较显然不会调用 Test.equals(Test) 函数。)这是因为 groovy 确实进行了完全动态类型。这特别令人感兴趣,因为它没有任何显式动态类型的变量。我在几个地方读到这被认为是有害的,因为程序员希望 groovy 做 java 的事情。

【讨论】:

  • 不幸的是,Groovy 付出的代价是巨大的性能损失,因为每个方法调用都使用反射。期望一种语言与另一种语言完全相同通常被认为是有害的。需要注意差异。
  • 在 JDK7 中使用 invokedynamic 应该会很好而且很快(或者甚至在今天使用类似的实现技术)。
【解决方案4】:

Java 不支持参数的协变,只支持返回类型。

换句话说,虽然您在覆盖方法中的返回类型可能是它在覆盖方法中的子类型,但对于参数而言并非如此。

如果您在 Object 中的 equals 参数是 Object,则在子类中将 equals 与其他任何东西放在一起将是重载,而不是被覆盖的方法。因此,调用该方法的唯一情况是参数的静态类型为 Test 时,如 T3 的情况。

祝面试过程顺利!我很想在一家提出这类问题的公司接受面试,而不是我教给学生的常见算法/数据结构问题。

【讨论】:

  • 你的意思是逆变参数。
  • 我不知何故完全掩盖了这样一个事实,即不同的方法参数本质上会创建一个重载方法,而不是一个被覆盖的方法。哦,别担心,还有算法/数据结构问题。 :P 谢谢你的好运,我需要它! :)
【解决方案5】:

我认为关键在于 equals() 方法不符合标准:它接受另一个 Test 对象,而不是 Object 对象,因此没有覆盖 equals() 方法。这意味着您实际上只是在给它 Test 对象同时给它 Object 对象调用 Object.equals(Object o) 时重载它来做一些特殊的事情。通过任何 IDE 查看该代码应该会显示两个用于测试的 equals() 方法。

【讨论】:

  • 这个,大部分的回复都没有抓住重点。问题不在于使用重载而不是覆盖这一事实。这就是为什么 t1.equals(t3) 不使用重载方法的原因,当 t1 声明为 Object 但初始化为 Test 时。
【解决方案6】:

方法被重载而不是被覆盖。 Equals 总是将 Object 作为参数。

顺便说一句,你在 Bloch 的有效 java 中有一个项目(你应该拥有)。

【讨论】:

  • Joshua Bloch 的有效 Java?
  • 有效,是的,打字的时候在想别的东西:D
【解决方案7】:

Dynamic Binding (DD) 和 Static Binding̣̣̣(SB) 中的一些注释,经过一段时间的搜索:

1.定时执行:(参考1)

  • DB:运行时
  • SB:编译器时间

2.用于

  • 数据库:覆盖
  • SB:重载(静态、私有、最终)(参考 2)

参考:

  1. 执行更喜欢使用哪种方法的平均解析器
  2. 因为不能用修饰符 static、private 或 final 覆盖方法
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

【讨论】:

    【解决方案8】:

    如果添加了另一个覆盖而不是重载的方法,它将在运行时解释动态绑定调用。

    /* 下面程序的输出是什么? */

    public class DynamicBinding {
        public boolean equals(Test other) {
            System.out.println("Inside of Test.equals");
            return false;
        }
    
        @Override
        public boolean equals(Object other) {
            System.out.println("Inside @override: this is dynamic binding");
            return false;
        }
    
        public static void main(String[] args) {
            Object t1 = new Test();
            Object t2 = new Test();
            Test t3 = new Test();
            Object o1 = new Object();
    
            int count = 0;
            System.out.println(count++);// prints 0
            t1.equals(t2);
            System.out.println(count++);// prints 1
            t1.equals(t3);
            System.out.println(count++);// prints 2
            t3.equals(o1);
            System.out.println(count++);// prints 3
            t3.equals(t3);
            System.out.println(count++);// prints 4
            t3.equals(t2);
        }
    }
    

    【讨论】:

      【解决方案9】:

      我发现了一篇关于动态与静态绑定的有趣文章。它带有一段用于模拟动态绑定的代码。它使我的代码更具可读性。

      https://sites.google.com/site/jeffhartkopf/covariance

      【讨论】:

        【解决方案10】:

        “为什么”这个问题的答案Java语言就是这样定义的。

        引用Wikipedia article on Covariance and Contravariance

        实现了返回类型协方差 在 Java 编程语言中 版本 J2SE 5.0。参数类型有 完全相同(不变) 方法覆盖,否则 方法被并行重载 而是定义。

        其他语言不同。

        【讨论】:

        • 我的问题大致相当于看到3+3写9,然后看到1+1写2。我明白Java语言是怎么定义的了;在这种情况下,无论出于何种原因,我完全将方法误认为不是,尽管我在同一问题的其他地方避免了该错误。
        【解决方案11】:

        很清楚,这里没有覆盖的概念。它是方法重载。 Object 类的Object() 方法采用Object 类型的引用参数,而此equal() 方法采用Test 类型的引用参数。

        【讨论】:

          【解决方案12】:

          我将尝试通过两个示例来解释这一点,这些示例是我在网上遇到的一些示例的扩展版本。

          public class Test {
          
              public boolean equals(Test other) {
                  System.out.println("Inside of Test.equals");
                  return false;
              }
          
              @Override
              public boolean equals(Object other) {
                  System.out.println("Inside of Test.equals ot type Object");
                  return false;
              }
          
              public static void main(String[] args) {
                  Object t1 = new Test();
                  Object t2 = new Test();
                  Test t3 = new Test();
                  Object o1 = new Object();
          
                  int count = 0;
                  System.out.println(count++); // prints 0
                  o1.equals(t2);
          
                  System.out.println("\n" + count++); // prints 1
                  o1.equals(t3);
          
                  System.out.println("\n" + count++);// prints 2
                  t1.equals(t2);
          
                  System.out.println("\n" + count++);// prints 3
                  t1.equals(t3);
          
                  System.out.println("\n" + count++);// prints 4
                  t3.equals(o1);
          
                  System.out.println("\n" + count++);// prints 5
                  t3.equals(t3);
          
                  System.out.println("\n" + count++);// prints 6
                  t3.equals(t2);
              }
          }
          

          这里,对于计数值为 0、1、2 和 3 的行;我们在equals() 方法上有o1t1Objectreference。因此,在编译时,Object.class 文件中的equals() 方法将被绑定。

          然而,即使t1referenceObject,它也有Test类的initialization
          Object t1 = new Test();.
          因此,在运行时它调用public boolean equals(Object other) 这是一个

          重写方法

          现在,对于 4 和 6 的计数值,具有 referenceinitialization Test 的 t3 再次调用 @ 987654330@ 方法,参数为 Object 引用,是一个

          重载方法

          好的!

          再次,为了更好地理解编译器将调用什么方法,只需 单击该方法,Eclipse 将突出显示类似的方法 它认为将在编译时调用的类型。如果没有得到 在编译时调用然后这些方法是方法的一个例子 覆盖。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-01-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-04-03
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多