javainheritanceoverridingoverloadingmethod-invocation

Why does it seem to call the wrong method?


Lets say I have two classes A and B . B inherits from A and B has the following methods:

public boolean equals(Object other) {
   System.out.print("Object");
   return true;
}
public boolean equals(A other){
   System.out.print("A object");
   return true;
} 
public boolean equals(B other) {
   System.out.print("B object");
   return true;
}
A a1 = new A();
A ab = new B();
B b1 = new B();

what is unclear to me is why

 ab.equals(a1)
 ab.equals(b1)

Returns Object

ab is an instance of B with pointer of A. a1 is clearly both instance and pointer of A. So Why does it use the Object other instead of A other method?? same goes for b1 which is an instance of B with pointe B yet the compiler chose to apply the equals method like I inserted regular object?? AM i that stupid or is this language incoherent?

BTW A doesn't have any equals methods at all.


Solution

  • Explanation

    BTW A doesn't have any equals methods at all.

    But you do

    ab.equals(a1)
    ab.equals(b1)
    

    And ab is:

    A ab = new B();
    

    So while it is actually a B, the variable is of type A.

    Javas rules for choosing methods in such situations will start looking for an equals method in the A class. And it actually has one, the default implementation inherited from Object. It has the signature

    public boolean equals(Object other)
    

    So it decides for this signature. Now it asks the actual instance behind ab, which is of type B, for a method with that signature. It chooses the overriden implementation in B:

    public boolean equals(Object other) {
       System.out.print("Object");
    }
    

    And consequentially prints Object in both cases.


    Details

    The details for this are defined in JLS§15.12. Method Invocation Expressions. It talks about finding the most specific match. The rules can get quite complex if you dive deep into it.

    Some excerpts:

    The first step in processing a method invocation at compile time is to figure out the name of the method to be invoked and which class or interface to search for definitions of methods of that name.

    The second step searches the type determined in the previous step for member methods. This step uses the name of the method and the argument expressions to locate methods that are both accessible and applicable, that is, declarations that can be correctly invoked on the given arguments.

    There may be more than one such method, in which case the most specific one is chosen. The descriptor (signature plus return type) of the most specific method is the one used at run time to perform the method dispatch.

    The class or interface determined by compile-time step 1 (§15.12.1) is searched for all member methods that are potentially applicable to this method invocation; members inherited from superclasses and superinterfaces are included in this search.

    If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen.