Search code examples
javainheritancedynamic-bindingstatic-binding

Java static and dynamic binding, upcast, overloading mixed together


Let's say that we have the following code

class TestEqual{
    public boolean equals(TestEqual other ) {
    System.out.println( "In equals from TestEqual" ); return false;
}

    public static void main( String [] args ) {
        Object t1 = new TestEqual(), t2 = new TestEqual();
        TestEqual t3 = new TestEqual();
        Object o1 = new Object();

        int count = 0;
        System.out.println( count++ );// shows 0
        t1.equals( t2 ) ;
        System.out.println( count++ );// shows 1
        t1.equals( t3 );
        System.out.println( count++ );// shows 2
        t3.equals( o1 );
        System.out.println( count++ );// shows 3
        t3.equals(t3);
        System.out.println( count++ );// shows 4
        t3.equals(t2);
    }
}

Basically, in the TestEqual class (that of course, extends Object) we have a method that is overloading the equals method from Object.

Also, we have some variables: Object t1, t2 instanced as TestEqual, TestEqual t3 instanced as TestEqual and an Object o1 instanced as an Object.

If we run the program, this will be the output.

0
1
2
3
In equals from TestEqual
4

This example seems a bit more complicated than the usual Car c = new Vehicle(); c.drive(); because the object from where we call the method is instanced different from its type, and also the parameter of the method is instanced different than its type.

I would like to check if I understood correctly what happens when we call each method, step-by-step regarding binding.

 show 0
 t1.equals(t2)
 show 1

t1 is considered as a TestEqual object. The method equals is overloaded, so the binding is static, this means that we will pass t2 as an Object, so it will call the equals method inherited from the Object superclass, so it will not show any text.

 show 1
 t1.equals(t3)
 show 2

This seems a bit weird. I would have expected to show "In equals from TestEqual", because t3 is a TestEqual object, so the equals from t1 should be called. My explanation here would be that t1 is static bound and considered as an Object, so the method equals inherited from the Object class is called, the parameter TestEqual t3 being upcast to Object. But wouldn't this mean that the previous explanation from t1.equals(t2) is wrong?

show 2
t3.equals(o1);
show 3

t3 is a TestEqual object, the parameter o1 is an Object, so the equals method inherited from Object is called, thus nothing is printed.

show 3
t3.equals(t3)
show 4

t3 is a TestEqual object, the parameter is a TestEqual object, so the overloaded method from TestEqual will be called, printing "In equals from TestEqual".

show 4
t3.equals(t2)

t3 is a TestEqual object, the parameter is an Object due to static binding (overloaded method), so the equal method inherited from Object is called, printing nothing.


Solution

  • This seems a bit weird. I would have expected to show "In equals from TestEqual", because t3 is a TestEqual object, so the equals from t1 should be called. My explanation here would be that t1 is static bound and considered as an Object, so the method equals inherited from the Object class is called, the parameter TestEqual t3 being upcast to Object. But wouldn't this mean that the previous explanation from t1.equals(t2) is wrong?

    To call a method in the context of overloading, most specific method invocation is happened and it is determined while compile time. The rule to chose the most specific method is defined in java language specification 15.12.2.5. Choosing the Most Specific Method: with other discussion, a mentioned statement is:

    A method m1 is strictly more specific than another method m2 if and only if m1 is more specific than m2 and m2 is not more specific than m1.

    To explain your context, however lets declare two simple Super class and sup class:

    class SuperA
    {
        public void test(SuperA a)
        {
            System.out.println("super class's test() is called");
        }
    }
    
    class SubB extends SuperA
    {
    
        public void test(SubB subB)
        {
            System.out.println("subclass's test() is called");
    
    
        }    
    }
    

    Now, If we create two instance this way:

    SuperA obj = new SubB();
    SubB obj2 = new SubB();
    obj.test(obj2);
    

    You will see that super class's test() is called, because it is determined as more specific at compile time and compiler sees that obj is an instance of type SuperA. Now cast the obj to SuubB and invoke test(obj2):

    ((SubB)obj).test(obj2); // cast to SubB
    

    And it print: "subclass's test() is called" implying that it calls the test(obj) method of SubB because this time compiler knows that obj has the type of SubB and most specific resolution for invoking test.

    However, Now lets declare two instance this way:

       SuperA obj = new SubB();
       SuperA obj2 = new SubB();
       obj.test(obj2); // invokes super class's test method
       ((SubB)obj).test(obj2);// invokes super class's test method
       ((SubB)obj).test((SubB)obj2); // invoke sub class's test method
    

    In this series of invocation, the first two statement will invoke super class SuperA's test method because these invocation is more specific. To explain the second case however:

    ((SubB)obj).test(obj2);// invokes super class's test method
    

    This time compiler knows that obj has the type of SubB but it still sees that obj2 has the type of SperA which is more specific for test method invocation. Hence for second example, in which both obj and obj2 was declared with type SuperA: we will need both of them to cast to SubB to invoke SubB'S test method.

    As Object is the super class of all the class in java, you should understand by now, however the equal method invocation system of your context. To avoid this kind of invocation pitfalls, you will see that all the implemented equal method in classes of java is actually override of equal method Object class and made use of instanceof checking. For example, the equal method implementation of Integer class:

    public boolean equals(Object obj) {
            if (obj instanceof Integer) { //<<<---- instance of checking
                return value == ((Integer)obj).intValue();
            }
            return false;
        }