Java动态绑定和方法重写
昨天我参加了一个两个小时的技术电话面试(我通过了,哇哦!),但我完全掩盖了以下有关 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()
方法),动态绑定应该 根据引用的实例化类型来调用该方法的最具体版本。 我缺少什么?
Yesterday I had a two-hour technical phone interview (which I passed, woohoo!), but I completely muffed up the following question regarding dynamic binding in Java. And it's doubly puzzling because I used to teach this concept to undergraduates when I was a TA a few years ago, so the prospect that I gave them misinformation is a little disturbing...
Here's the problem I was given:
/* 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);
}
}
I asserted that the output should have been two separate print statements from within the overridden equals()
method: at t1.equals(t3)
and t3.equals(t3)
. The latter case is obvious enough, and with the former case, even though t1
has a reference of type Object, it is instantiated as type Test, so dynamic binding should call the overridden form of the method.
Apparently not. My interviewer encouraged me to run the program myself, and lo and behold, there was only a single output from the overridden method: at the line t3.equals(t3)
.
My question then is, why? As I mentioned already, even though t1
is a reference of type Object (so static binding would invoke Object's equals()
method), dynamic binding should take care of invoking the most specific version of the method based on the instantiated type of the reference. What am I missing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
Java 对重载方法使用静态绑定,对重写方法使用动态绑定。 在您的示例中,equals 方法被重载(具有与 Object.equals() 不同的参数类型),因此调用的方法在编译时绑定到 reference 类型。
此处的一些讨论
事实上,它是 equals 方法并不真正相关,其他重载而不是覆盖它是一个常见的错误,根据您在面试中对问题的回答,您已经意识到了这一点。
编辑:
此处也有很好的描述。 此示例显示了与参数类型相关的类似问题,但由同一问题引起。
我相信如果绑定实际上是动态的,那么调用者和参数是 Test 实例的任何情况都会导致调用重写的方法。 因此 t3.equals(o1) 将是唯一不会打印的情况。
Java uses static binding for overloaded methods, and dynamic binding for overridden ones. In your example, the equals method is overloaded (has a different param type than Object.equals()), so the method called is bound to the reference type at compile time.
Some discussion here
The fact that it is the equals method is not really relevant, other than it is a common mistake to overload instead of override it, which you are already aware of based on your answer to the problem in the interview.
Edit:
A good description here as well. This example is showing a similar problem related to the parameter type instead, but caused by the same issue.
I believe if the binding were actually dynamic, then any case where the caller and the parameter were an instance of Test would result in the overridden method being called. So t3.equals(o1) would be the only case that would not print.
Test
的equals
方法不会覆盖java.lang.Object
的equals
方法。 看参数类型!Test
类使用接受Test
的方法重载equals
。如果要重写 equals 方法,则应使用 @Override 注释。 这会导致编译错误指出这个常见错误。
The
equals
method ofTest
does not override theequals
method ofjava.lang.Object
. Look at the parameter type! TheTest
class is overloadingequals
with a method that accepts aTest
.If the
equals
method is intended to override, it should use the @Override annotation. This would cause a compilation error to point out this common mistake.有趣的是,在 Groovy 代码(可以编译为类文件)中,除了一个调用之外的所有调用都将执行 print 语句。 (将测试与对象进行比较的方法显然不会调用 Test.equals(Test) 函数。)这是因为 groovy 确实执行完全动态类型。 这是特别令人感兴趣的,因为它没有任何显式动态类型的变量。 我在几个地方读到这被认为是有害的,因为程序员期望 groovy 做 java 的事情。
Interestingly enough, in Groovy code (which could be compiled to a class file), all but one of the calls would execute the print statement. (The one comparing a Test to an Object clearly won't call the Test.equals(Test) function.) This is because groovy DOES do completely dynamic typing. This is particularly of interest because it does not have any variables that are explicitly dynamically typed. I have read in a couple of places that this is considered harmful, as programmers expect groovy to do the java thing.
Java 不支持参数的协变,仅支持返回类型。
换句话说,虽然重写方法中的返回类型可能是被重写方法中的返回类型的子类型,但对于参数来说却并非如此。
如果 Object 中的 equals 参数是 Object,则将 equals 与子类中的其他任何内容一起放置将是重载方法,而不是重写方法。 因此,只有当参数的静态类型为 Test 时才会调用该方法,如 T3 的情况。
祝面试过程顺利! 我很乐意接受一家提出此类问题的公司的面试,而不是我教给学生的常见算法/数据结构问题。
Java does not support co-variance in parameters, only in return types.
In other words, while your return type in an overriding method may be a subtype of what it was in the overridden, that is not true for parameters.
If your parameter for equals in Object is Object, putting an equals with anything else in a subclass will be an overloaded, not an overridden method. Hence, the only situation where that method will be called is when the static type of the parameter is Test, as in the case of T3.
Good luck with the job interview process! I'd love to be interviewed at a company that asks these types of questions instead of the usual algo/data structures questions that I teach my students.
我认为关键在于 equals() 方法不符合标准:它接受另一个 Test 对象,而不是 Object 对象,因此不会覆盖 equals() 方法。 这意味着您实际上只在给定 Test 对象并给它 Object 对象调用 Object.equals(Object o) 时重载它来执行一些特殊操作。 通过任何 IDE 查看代码应该会显示两个用于测试的 equals() 方法。
I think the key lies in the fact that the equals() method doesn't conform to standard: It takes in another Test object, not Object object and thus isn't overriding the equals() method. This means you actually have only overloaded it to do something special when it's given Test object while giving it Object object calls Object.equals(Object o). Looking that code through any IDE should show you two equals() methods for Test.
该方法被重载而不是被覆盖。 等于始终将对象作为参数。
顺便说一句,您在 Bloch 的 effective java 中有一个关于此的项目(您应该拥有)。
The method is overloaded instead of overriden. Equals always take an Object as parameter.
btw, you have an item on this in Bloch's effective java (that you should own).
搜索一段时间后,动态绑定(DD)和静态绑定̣̣̣(SB)中的一些注意事项:
1.定时执行:(Ref.1)
2.用于:
参考:
Some note in Dynamic Binding (DD) and Static Binding̣̣̣(SB) after search a while:
1.Timing execute: (Ref.1)
2.Used for:
Reference:
如果添加另一个方法来重写而不是重载,它将解释运行时的动态绑定调用。
/* 以下程序的输出是什么? */
If another method is added that overrides instead of overloading it will explain the dynamic binding call at run time.
/* What is the output of the following program? */
我发现了一篇关于动态与静态绑定的有趣文章。 它附带了一段用于模拟动态绑定的代码。 它使我的代码更具可读性。
https://sites.google.com/site/jeffhartkopf/covariance
I found an interesting article about dynamic vs. static binding. It comes with a piece of code for simulating dynamic binding. It made my code a more readable.
https://sites.google.com/site/jeffhartkopf/covariance
“为什么?”这个问题的答案 Java 语言就是这样定义的。
引用关于协方差和逆变的维基百科文章:
其他语言则不同。
The answer to the question "why?" is that's how the Java language is defined.
To quote the Wikipedia article on Covariance and Contravariance:
Other languages are different.
很明显,这里没有覆盖的概念。 这是方法重载。
Object 类的
Object()
方法采用 Object 类型的引用作为参数,而这个equal()
方法采用 Test 类型的引用作为参数。It's very clear, that there is no concept of overriding here. It is method overloading.
the
Object()
method of Object class takes parameter of reference of type Object and thisequal()
method takes parameter of reference of type Test.我将尝试通过两个示例来解释这一点,这两个示例是我在网上遇到的一些示例的扩展版本。
这里,对于计数值为0、1、2和3的行; 我们在
equals()
方法上有 o1 和 t1 的 Object 的引用 。 因此,在编译时,Object.class 文件中的equals()
方法将受到限制。但是,即使t1的引用是对象,它也具有测试类的初始化 。
对象 t1 = new Test();
。因此,在运行时它调用
public boolean equals(Object other)
这是一个。
现在,对于计数值为 4 和 6 的情况,t3 也很简单,它具有 引用 和初始化 Test 正在调用
equals()
方法,参数作为对象引用,并且是一个OK!
I will try to explain this through two examples which are the extended versions of some of the examples that I came across online.
Here, for lines with count values 0, 1, 2, and 3; we have reference of Object for o1 and t1 on the
equals()
method. Thus, at compile time, theequals()
method from the Object.class file will be bounded.However, even though reference of t1 is Object, it has intialization of Test class.
Object t1 = new Test();
.Therefore, at run-time it calls the
public boolean equals(Object other)
which is an.
Now, for count values as 4 and 6, it is again straightforward that t3 which has reference and initialization of Test is calling
equals()
method with parameter as Object references and is anOK!