Java动态绑定和方法覆盖


89

昨天我接受了两个小时的技术电话面试(我通过了,woohoo!),但是我完全想出了以下有关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()方法中的两个单独的打印语句:at t1.equals(t3)t3.equals(t3)。后一种情况很明显,而在前一种情况下,即使t1具有Object类型的引用,也将其实例化为Test类型,因此动态绑定应调用该方法的重写形式。

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

我的问题是,为什么?正如我已经提到的,即使t1是对Object类型的引用(因此静态绑定将调用Object的equals()方法),动态绑定也根据引用的实例化类型来调用方法的最特定版本。我想念什么?


请找到我对此答案的帖子,在其中我已尽力解释了其他情况。非常感谢您的投入:)
Devendra Lattu

Answers:


81

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

这里一些讨论

它是equals方法的事实并没有什么实际意义,除了重载而不是覆盖它是一个常见的错误之外,您已经根据面试中对问题的答案知道了这一点。

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

我相信,如果绑定实际上是动态的,则在任何情况下调用方和参数都是Test实例的情况都将导致调用重写的方法。因此,t3.equals(o1)将是唯一无法打印的情况。


许多人指出,它已经过载并且没有被覆盖,但是即使如此,您仍然希望它能够正确解决过载的情况。据我所知,您的帖子实际上是到目前为止唯一可以正确回答问题的帖子。
Bill K

4
我的错误完全遗漏了该方法确实是重载而不是被覆盖的事实。我看到“ equals()”,立即想到继承和重写。看起来我还是再次理解了更广泛,更困难的概念,但搞砸了简单的细节。:P
Magsol

14
@Override注释存在的另一个原因。
马特

1
在我之后重复
一遍

1
所以我不知道毕业了 谢谢!
Atieh 2014年

25

equals方法Test不会覆盖的equals方法java.lang.Object。看参数类型!所述Test类被重载equals用一个接受的方法Test

如果equals要覆盖该方法,则应使用@Override批注。这将导致编译错误,以指出此常见错误。


是的,我不太确定为什么我错过了这个简单而关键的细节,但这正是我的问题所在。谢谢!
Magsol

+1是对提问者好奇结果的真实答案
matt b

请找到我对此答案的帖子,在其中我已尽力解释了其他情况。非常感谢您的投入:)
Devendra Lattu

6

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


1
不幸的是,Groovy付出的代价是对性能的巨大打击,因为每种方法调用都使用反射。通常,期望一种语言与另一种语言完全相同地工作是有害的。人们需要意识到差异。
Joachim Sauer,

使用JDK7中的invokedynamic应该很好,快速(甚至使用今天的类似实现技术)。
Tom Hawtin-大头钉

5

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

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

如果Object中equals的参数是Object,则将equals与其他任何内容放在子类中将是重载,而不是覆盖的方法。因此,仅在参数的静态类型为Test时才调用该方法,就像T3一样。

面试过程祝您好运!我很想在一家问这些类型的问题而不是我教我的学生的常规算法/数据结构问题的公司接受采访。


1
您的意思是相反的参数。
汤姆·哈特芬

我以某种方式完全掩盖了以下事实:不同的方法参数本质上会创建一个重载的方法,而不是被覆盖的方法。哦,不用担心,还有算法/数据结构问题。:P谢谢您的好运,我需要它!:)
Magsol

4

我认为关键在于以下事实:equals()方法不符合标准:它接受另一个Test对象,而不是Object对象,因此不会覆盖equals()方法。这意味着实际上,在给它提供Test对象的同时,给它提供Object对象调用Object.equals(Object o)的功能时,实际上您只是对其进行了重载。通过任何IDE查看该代码都应向您显示两个用于测试的equals()方法。


这和大多数答复都没有指出重点。问题不在于使用重载而不是覆盖的事实。这就是为什么当t1声明为Object但初始化为Test时,t1.equals(t3)不使用重载方法的原因。
罗宾

4

该方法被重载而不是被重写。等于始终将对象作为参数。

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


Joshua Bloch的有效Java?
DJClayworth

是的,是的,是的,很有效,是在想其他事情:D
Gilles


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);
    }
}


0

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

引用维基百科有关协变和相反的文章

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

其他语言是不同的。


我的问题大致相当于看到3 + 3并写9,然后看到1 + 1并写2。在这种情况下,无论出于何种原因,即使我避免了同一问题的其他地方出现的错误,我都将方法完全误认为不是这种方法。
Magsol

0

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


-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的行;我们在方法上为o1t1 引用Object。因此,在编译时,将限制Object.class文件中的方法。 equals()equals()

然而,尽管参考T1对象,它具有intialization测试类
Object t1 = new Test();
因此,在运行时它调用public boolean equals(Object other)这是一个

覆盖方法

在此处输入图片说明

现在,对于4和6的计数值,再次很简单的一点是,具有Test的引用初始化t3正在调用参数为Object引用的方法,并且equals()

重载方法

好!

同样,为了更好地理解编译器将调用哪种方法,只需单击该方法,Eclipse将突出显示它认为在编译时将调用的类似类型的方法。如果在编译时未调用它,则这些方法就是方法重写的一个示例。

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.