Java code related to equals method

JavaEquals

Java Problem Overview


I am practicing for an exam, and found a sample problem that I don't understand.

For the following code, find what the output is:

public class Test {

    private static int count = 0;

    public boolean equals(Test testje) {
        System.out.println("count = " + count);
        return false;
    }

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

        ++count; t1.equals(t2);
        ++count; t1.equals(t3);
        ++count; t3.equals(o1);
        ++count; t3.equals(t3);
        ++count; t3.equals(t2);
    }
}

The output of this code is count = 4, but I don't understand why. Can anyone help me?

Java Solutions


Solution 1 - Java

The first thing you should note is that public boolean equals(Test testje) doesn't override Object's equals, since the argument is Test instead of Object, so the signatures don't match.

Therefore the main method calls equals(Test testje) exactly once - when executing t3.equals(t3); - since that's the only case in which both the static type of the instance equals is executed for and the type of the argument are the Test class.

t3.equals(t3); is the 4th equals statement (which comes after 4 increments of the static count variable), so 4 is printed.

All the other equals statements execute Object's equals, and therefore print nothing.

A more detailed explanation :

t1.equals() calls Object's equals regardless of the type of the argument, since the static (compile time) type of t1 is Object, and the Test class doesn't override that method. The Object class doesn't have an equals method with a single Test argument, so equals(Test testje) can't be called, regardless of the dynamic (runtime type) of t1.

t3.equals() can execute either Object's equals or Test's equals, since the compile time type of t3 is Test, and the Test class has two equals methods (one inherited from the Object class and the other defined in the Test class).

The method being chosen depends on the compile time type of the argument :

  1. When the argument is Object (as in t3.equals(o1); or t3.equals(t2);), Object's equals is called and nothing is printed.
  2. When the argument is Test, as in t3.equals(t3);, both versions of equals match that argument, but due to the rules of method overloading, the method with the most specific argument - equals(Test testje) - is chosen and the count variable is printed.

Solution 2 - Java

The equals method in Test takes an instance of Test.

All the previous attempts have been made with an instance of Object, which take the inherrited method from the Object class:

public boolean equals(Object o){
  return this == o;
}

Since there is no print in there, it won't print any value.

Your ++count; will increment the value of count, so the moment when you actually call your

public boolean equals(Test testje){...

method, that does print that value, the value of count is 4.

Solution 3 - Java

t3.equals(t3) is the only line which has the right arguments that match the method signature public boolean equals (Test testje) so it's the only line in the program which actually calls that print statement. This question is designed to teach you a few things.

  • All class implicitly extend Object
  • Object.java contains an equals method which takes type Object
  • multiple methods with the same name can exist provided they have different arguments - this is known as method overloading
  • the method method overload who's signature matches the arguments at runtime is the method that gets invoked.

Essentially the trick here is that Test implicitly extends Object like all java classes do. Object contains an equals method that takes type Object. t1 and t2 are typed such that at run time the arguments never match the method signature of equals that is defined in Test. Instead it's always calling into the equals method in Object.java because either the base type Is Object in which case the only methods you have access to are the ones defined in Object.java or the derived type is Object in which case

public boolean equals(Test testje)

Cannot be entered because in that case at runtime the argument is of type Object which is a Superclass of Test, not a subclass. So instead it looks at the equals method in the Test.java's implicitly typed superclass Object.java which also contains an equals method, which just happens to have a method signature of

public boolean equals (Object o)

which in this case match our arguments at runtime so this equals method is the one that executes.

Notice in the case of t3.equals(t3) both the base type and the derived type of t3 are Test.

Test t3 = new Test ();

this means that at runtime you are calling the equals method in Test.java and the argument you are passing in is actually of type Test so the method signatures match and the code inside Test.java executes. At this point count == 4.

Bonus bit of knowledge for you:

@Override 

annotation you may have seen in a few places explicitly instructs the compiler to fail if it does not find method with the exact same signature somewhere in a Superclass. This is useful to know if you definitely intend to override a method and you want to be absolutely sure that you really are overriding the method and you haven't accidentally changed the method in either the superclass or subclass but not both and Introduced a runtime error where the wrong implementation of the method is being called causing unwanted behavior.

Solution 4 - Java

There's two key things that you should know.

  • Overridden methods must have the exact signatures as their superclass have. (in your example this condition doesn't meet.)

  • In Java for an object, we have two types: compile type and runtime type. In the following example compile type of myobj is Object but its runtime type is Car.

     public class Car{
           @Override
           public boolean equals(Object o){
                 System.out.println("something");
                 return false;
           }
     }
    
    `Object myobj = new Car();`
    

Also you should note that myobj.equals(...) results in printing something in the console.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDries CoppensView Question on Stackoverflow
Solution 1 - JavaEranView Answer on Stackoverflow
Solution 2 - JavaStultuskeView Answer on Stackoverflow
Solution 3 - Javajames_s_taylerView Answer on Stackoverflow
Solution 4 - JavafrogattoView Answer on Stackoverflow