Java动态绑定和方法覆盖

时间:2008-11-26 19:26:22

标签: java inheritance dynamic-binding

昨天我接受了两小时的技术电话采访(我通过了,哇喔!),但是我完全忽略了关于Java中动态绑定的以下问题。这是令人费解的,因为几年前我曾经是TA的时候曾经教过这个概念,所以我给他们错误信息的前景有点令人不安......

这是我给出的问题:

/* 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()方法),动态绑定应该负责调用最具体的基于实例化引用类型的方法的版本。我错过了什么?

13 个答案:

答案 0 :(得分:81)

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

一些讨论here

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

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

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

答案 1 :(得分:25)

equals的{​​{1}}方法未覆盖Test的{​​{1}}方法。看一下参数类型! equals类使用接受java.lang.Object的方法重载Test

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

答案 2 :(得分:6)

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

答案 3 :(得分:5)

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

换句话说,虽然覆盖方法中的返回类型可能是覆盖的内容的子类型,但参数不是这样。

如果您在Object中等于equals的参数是Object,那么在子类中放置一个与其他任何东西相等的将是重载的,而不是重写的方法。因此,调用该方法的唯一情况是参数的静态类型为Test,如T3的情况。

祝你在求职面试过程中好运!我很乐意接受一家公司的采访,这家公司会询问这些类型的问题,而不是我教给学生的通常的算法/数据结构问题。

答案 4 :(得分:4)

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

答案 5 :(得分:4)

该方法已重载而非覆盖。等于总是将Object作为参数。

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

答案 6 :(得分:4)

在搜索一段时间后,动态绑定(DD)和静态绑定(SB)中的一些注意事项:

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

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

2.适用于

  • DB:overriding
  • SB:重载(静态,私有,最终)(参考2)

<强>参考:

  1. 执行方法更喜欢使用的平均解析器
  2. 因为无法用修饰符覆盖静态,私有或最终的方法
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

答案 7 :(得分: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);
    }
}

答案 8 :(得分:1)

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

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

答案 9 :(得分:0)

另见此SO问题,密切相关:Overriding the JAVA equals method quirk

答案 10 :(得分:0)

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

引用Wikipedia article on Covariance and Contravariance

  

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

其他语言不同。

答案 11 :(得分:0)

很明显,这里没有压倒一切的概念。这是方法重载。 Object类的Object()方法接受Object类型的引用参数,这个equal()方法接受Test类型的引用参数。

答案 12 :(得分:-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的行;我们在equals()方法上为 o1 t1 引用。因此,在编译时, Object.class 文件中的equals()方法将受到限制。

但是,即使 t1 引用对象,它仍然具有测试类的初始化 即可。
Object t1 = new Test();
因此,在运行时它会调用public boolean equals(Object other)

  

重写方法

enter image description here

现在,对于计数值为4和6,再次直接 t3 ,其中引用初始化的Test正在调用{ {1}}方法,参数为Object引用,是

  

重载方法

OK!

  

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

enter image description here