3

In the code below why is it that o1.equals(o2); calls equals(Object o) not the equals(EqualsTest et) even though o1 and o2 are referencing objects of type EqualsTest!

public class EqualsTest {
      public static <T> boolean equalTest(T o1, T o2) {
          return o1.equals(o2);
      }
      public static void main(String[] args) {
          EqualsTest et1 = new EqualsTest();
          EqualsTest et2 = new EqualsTest();
          System.out.println(et1.equals(et2));
          System.out.println(equalTest(et1, et2));
      }
      public boolean equals(Object o) {
          if (o instanceof EqualsTest) {
              System.out.println("equals(Object)");
              return true;
          }
          return false;
      }
      public boolean equals(EqualsTest et) {
          System.out.println("equals(EqualsTest)");
          return this.equals((Object)et);
      }
}
5
  • Type Erasure is doing its job Commented Nov 5, 2012 at 17:22
  • 1
    You should never write an equals(X) method except for equals(Object), FYI. It'll only get you confused, and won't get used when you expect it to. Commented Nov 5, 2012 at 17:31
  • @LouisWasserman yes, this is where im confused on. I see methods written with Object params all the time, but if I knew I'm going to pass in an EqualsTest object, why can't I get specific and set the param to EqualsTest? Commented Nov 5, 2012 at 17:38
  • Because all the other code that uses equals -- like HashSet -- has to worry about the case where two objects of different types can be unequal, so it has to use the equals(Object) version, and the overload rules mean that it won't call your equals(EqualsTest) version. It's sort of weird at first, but it really is the only approach that makes sense in all cases. Commented Nov 5, 2012 at 18:38
  • @LouisWasserman does that mean if i declared public static <EqualsTest> boolean equalTest(EqualsTest o1, EqualsTest o2), then the program would call equals(EqualsTest et)? Commented Nov 5, 2012 at 18:41

3 Answers 3

1

The compiler finds the corresponding method based on the declared type of the argument, not the most specific one. Since you didn't specify anything for thee T, it defaults to Object, as @nicholas.hauschild correctly points out.

Sign up to request clarification or add additional context in comments.

8 Comments

What is the declared type one might ask? In this scenario, it is <T>, which we have no further information about, so the only thing we can infer is that it is an Object. +1
@nicholas.hauschild but didn't we referenced et2 = new EqualsTest() so the compiler would know that it is equals(EqualsTest et) that should be called... that's where im confused
No, once you are in the static generic method, this knowledge is "lost" since the parameters are just typed as Object.
@ShiDoiSi if what you said is true, then there's no point in writing code (ever?) with equals(SomeClass obj) every method that deals with a class param must be written in methodName(Object obj)
@user133466 It will call this method if the argument type matches exactly at compile time, e.g. because a variable is declared as that type, or an explicit cast.
|
1

As I mentioned in the comment it is because of TypeErasure in java.

Check the byte code which is generated for equalTest. You can clearly see it will invoke method which has Object as parameter.

It is same as calling this.equals((Object)et) which will invoke Object method

// Method descriptor #15 (Ljava/lang/Object;Ljava/lang/Object;)Z
// Signature: <T:Ljava/lang/Object;>(TT;TT;)Z
// Stack: 2, Locals: 2
public static boolean equalTest(java.lang.Object o1, java.lang.Object o2);
0  aload_0 [o1]
1  aload_1 [o2]
2  invokevirtual java.lang.Object.equals(java.lang.Object) : boolean [18]

What you need is

public static <T extends EqualsTest> boolean equalTest(T o1, T o2) {
    return o1.equals(o2);
}

Now Lets check the generated byte code.

public static boolean equalTest(EqualsTest o1, EqualsTest o2);
0  aload_0 [o1]
1  aload_1 [o2]
2  invokevirtual EqualsTest.equals(EqualsTest) : boolean [18]
5  ireturn

As you can see compiler has changed Object to specific type that is EqualsTest because we have used Bounded Type so now if you invoke equalTest it will invoke method with equalTest as parameter

2 Comments

His confusion is that Java doesn't use the runtime-type of arguments for dispatching. But your answer is helpful in clarifying that the missing type instantiation for T turns into Object.
@user133466 You can look at the generated bytecode using javap: docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javap.html
1

Since you have used Overloading the methods are linked at compile time and Java uses the less specific argument type for the polymorphic binding to the callee which is object in this case and not EqualsTest.

5 Comments

-1, least specific != most precise at compile time, which is what actually happens. (And I don't find that article particularly readable ;)
What you mean by "most precise"?
To clarify: the compiler chooses Object, because this is the only type it has at compile type. If it has a "better" one like in @AmitD's answer, it will use that one, not a least specific as you are conjecturing (which would still be Object). Or did you mean "...the less specific..."?
i meant the less specific the compiler know of. Is that correct?
Try to elaborate your answer...this is about precision (literally), so also some care in formulating is required (as you rightfully pointed out before).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.