90

昨天我有一个两个小时的技术电话面试(我通过了,哇哦!),但我完全掩盖了以下关于 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()方法中的两个单独的打印语句: att1.equals(t3)t3.equals(t3). 后一种情况很明显,而前一种情况,即使t1有一个Object类型的引用,它也被实例化为Test类型,所以动态绑定应该调用方法的覆盖形式。

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

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

4

12 回答 12

82

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

这里有一些讨论

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

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

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

于 2008-11-26T21:33:10.880 回答
26

equals方法Test不会覆盖 的equals方法java.lang.Object。看参数类型!该类Test使用equals接受Test.

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

于 2008-11-26T19:35:06.110 回答
7

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

于 2008-11-26T19:47:18.597 回答
5

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

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

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

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

于 2008-11-26T19:39:00.360 回答
4

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

于 2008-11-26T19:31:11.297 回答
4

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

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

于 2008-11-26T19:34:47.123 回答
4

搜索一段时间后,动态绑定(DD) 和静态绑定̣̣̣ (SB)中的一些注释:

1.定时执行:(Ref.1)

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

2.用于

  • DB:压倒一切
  • SB:重载(静态、私有、最终)(参考文献 2)

参考:

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

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

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

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);
    }
}
于 2013-10-30T21:38:14.900 回答
1

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

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

于 2012-07-12T20:04:17.190 回答
0

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

引用关于协方差和逆变的维基百科文章

返回类型协方差在 Java 编程语言版本 J2SE 5.0 中实现。方法重写的参数类型必须完全相同(不变),否则方法会被并行定义重载。

其他语言不一样。

于 2008-11-26T21:59:39.997 回答
0

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

于 2010-11-16T09:46:51.990 回答
-1

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

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 的行;我们在方法上有o1t1Object参考。因此,在编译时,来自Object.class文件的方法将是有界的。 equals()equals()

但是,即使t1引用Object,它也有Test类的初始化。 . 因此,在运行时它调用which 是
Object t1 = new Test();
public boolean equals(Object other)

被覆盖的方法

. 在此处输入图像描述

现在,对于 4 和 6 的计数值,具有引用初始化Test 的t3再次调用带有参数作为对象引用的方法,并且是equals()

重载方法

好的!

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

在此处输入图像描述

于 2017-04-03T10:16:27.393 回答