Java动态绑定和方法重写

发布于 2024-07-08 20:20:58 字数 1272 浏览 10 评论 0原文

昨天我参加了一个两个小时的技术电话面试(我通过了,哇哦!),但我完全掩盖了以下有关 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

始终不够 2024-07-15 20:20:58

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.

一向肩并 2024-07-15 20:20:58

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

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

The equals method of Test does not override the equals method of java.lang.Object. Look at the parameter type! The Test class is overloading equals with a method that accepts a Test.

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.

一萌ing 2024-07-15 20:20:58

有趣的是,在 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.

终陌 2024-07-15 20:20:58

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.

鲸落 2024-07-15 20:20:58

我认为关键在于 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.

旧梦荧光笔 2024-07-15 20:20:58

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

顺便说一句,您在 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).

苯莒 2024-07-15 20:20:58

搜索一段时间后,动态绑定(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

Some note in Dynamic Binding (DD) and Static Binding̣̣̣(SB) after search a while:

1.Timing execute: (Ref.1)

  • DB: at run time
  • SB: compiler time

2.Used for:

  • DB: overriding
  • SB: overloading (static, private, final) (Ref.2)

Reference:

  1. Execute mean resolver which method prefer to use
  2. Because can not overriding method with modifier static, private or final
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html
花落人断肠 2024-07-15 20:20:58

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

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

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

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? */

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);
    }
}
守不住的情 2024-07-15 20:20:58

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

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

没有心的人 2024-07-15 20:20:58

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

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

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

其他语言则不同。

The answer to the question "why?" is that's how the Java language is defined.

To quote the Wikipedia article on Covariance and Contravariance:

Return type covariance is implemented
in the Java programming language
version J2SE 5.0. Parameter types have
to be exactly the same (invariant) for
method overriding, otherwise the
method is overloaded with a parallel
definition instead.

Other languages are different.

骑趴 2024-07-15 20:20:58

很明显,这里没有覆盖的概念。 这是方法重载。
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 this equal() method takes parameter of reference of type Test.

等往事风中吹 2024-07-15 20:20:58

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

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() 方法上有 o1t1Object引用 。 因此,在编译时,Object.class 文件中的 equals() 方法将受到限制。

但是,即使t1引用对象,它也具有测试类的初始化

对象 t1 = new Test();

因此,在运行时它调用 public boolean equals(Object other) 这是一个

重写方法


输入图片这里的描述

现在,对于计数值为 4 和 6 的情况,t3 也很简单,它具有 引用初始化 Test 正在调用 equals() 方法,参数作为对象引用,并且是一个

重载方法

OK!

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

输入图片此处描述

I will try to explain this through two examples which are the extended versions of some of the examples that I came across online.

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

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, the equals() 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

overridden method

.
enter image description here

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 an

overloaded method

OK!

Again, to better understand what method the compiler will call, just
click on the method and Eclipse will highlight the methods of similar
types which it thinks will call at the compile time. If it doesn't get
called at compile time then those methods are an example of method
overridding.

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文