Java dynamic binding and method overriding
Asked Answered
B

12

90

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?

Baisden answered 26/11, 2008 at 19:26 Comment(1)
Kindly find my post to this answer where I have tried my best to explain with additional cases. I would really appreciate your inputs :)Underwater
H
82

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.

Holpen answered 26/11, 2008 at 21:33 Comment(5)
A lot of people point out that it's overloaded and not overridden, but even with that you'd expect it to resolve the overloaded one correctly. Your post is actually the only one so far that answers the question correctly as far as I can tell.Metts
My mistake was completely missing the fact that the method is indeed overloaded rather than overridden. I saw "equals()" and immediately thought inherited-and-overridden. Looks like I, yet again, got the broader and more difficult concept correct, but screwed up the simple details. :PBaisden
another reason the @Override annotation exists.Meza
Repeat after me : "Java uses static binding for overloaded methods, and dynamic binding for overridden ones" - +1Holinshed
Kindly find my post to this answer where I have tried my best to explain with additional cases. I would really appreciate your inputs :)Underwater
S
26

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.

Sudbury answered 26/11, 2008 at 19:35 Comment(3)
Yeah, I'm not quite sure why I missed that simple yet crucial detail, but that's exactly where my problem was. Thank you!Baisden
+1 for being the true answer to the questioner's curious resultsLowermost
Kindly find my post to this answer where I have tried my best to explain with additional cases. I would really appreciate your inputs :)Underwater
K
7

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.

Kaikaia answered 26/11, 2008 at 19:47 Comment(2)
Unfortunately the price that Groovy pays for that is a massive performance hit, as every method invocation uses reflection. Expecting one language to work exactly the same as another one is generally considered harmful. One needs to be aware of differences.Kylie
Should be nice and fast with invokedynamic in JDK7 (or even using similar implementation technique today).Graybill
D
5

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.

Distil answered 26/11, 2008 at 19:39 Comment(2)
You mean contravariant parameters.Graybill
I somehow completely glossed over the fact that different method parameters intrinsically create an overloaded method, not an overridden one. Oh don't worry, there were algo/data structures questions as well. :P And thank you for the good luck, I'll need it! :)Baisden
P
4

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.

Pigheaded answered 26/11, 2008 at 19:31 Comment(1)
This, and the most of the responses are missing the point. The issue is not about the fact that overloading is being used instead of overriding. It is why isn't the overloaded method used for t1.equals(t3), when t1 is declared as Object but initialized to Test.Holpen
M
4

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).

Mckinzie answered 26/11, 2008 at 19:34 Comment(2)
Joshua Bloch's Effective Java?Immovable
Effective yeah, was thinking of something else while typing :DMckinzie
B
4

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
Beria answered 16/3, 2012 at 9:19 Comment(0)
S
2

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);
    }
}
Sweetscented answered 30/10, 2013 at 21:38 Comment(0)
V
1

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

Vibrations answered 12/7, 2012 at 20:4 Comment(0)
G
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.

Guberniya answered 26/11, 2008 at 21:59 Comment(1)
My problem was roughly equivalent to seeing 3+3 and writing 9, then seeing 1+1 and writing 2. I understand how the Java language is defined; in this case, for whatever reason, I completely mistook the method for something it wasn't, even though I avoided that mistake elsewhere in the same problem.Baisden
A
0

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.

Assisi answered 16/11, 2010 at 9:46 Comment(0)
U
-1

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

Underwater answered 3/4, 2017 at 10:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.