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);
      }
}
Volker Stolz
  • 7,274
  • 1
  • 32
  • 50
user133466
  • 3,391
  • 18
  • 63
  • 92
  • `Type Erasure` is doing its job – Amit Deshpande Nov 05 '12 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. – Louis Wasserman Nov 05 '12 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? – user133466 Nov 05 '12 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. – Louis Wasserman Nov 05 '12 at 18:38
  • @LouisWasserman does that mean if i declared `public static boolean equalTest(EqualsTest o1, EqualsTest o2)`, then the program would call `equals(EqualsTest et)`? – user133466 Nov 05 '12 at 18:41

3 Answers3

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.

Community
  • 1
  • 1
Volker Stolz
  • 7,274
  • 1
  • 32
  • 50
  • What is the declared type one might ask? In this scenario, it is , which we have no further information about, so the only thing we can infer is that it is an `Object`. +1 – nicholas.hauschild Nov 05 '12 at 17:26
  • @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 – user133466 Nov 05 '12 at 17:39
  • No, once you are in the static generic method, this knowledge is "lost" since the parameters are just typed as `Object`. – Volker Stolz Nov 05 '12 at 17:44
  • @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 Nov 05 '12 at 18:13
  • @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. – Volker Stolz Nov 05 '12 at 18:14
  • @ShiDoiSi still a bit confused, can we continue in chat? – user133466 Nov 05 '12 at 18:16
  • does that mean if i declared `public static boolean equalTest(EqualsTest o1, EqualsTest o2)`, then the program would call `equals(EqualsTest et)`? – user133466 Nov 05 '12 at 18:31
  • @user133466 The `<..>` then no longer making sense. Yes, then it'll work that way. – Volker Stolz Nov 05 '12 at 18:47
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.

Santosh Gokak
  • 3,393
  • 3
  • 22
  • 24
  • -1, least specific != most precise at compile time, which is what actually happens. (And I don't find that article particularly readable ;) – Volker Stolz Nov 05 '12 at 17:53
  • What you mean by "most precise"? – Santosh Gokak Nov 05 '12 at 17:57
  • 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..."? – Volker Stolz Nov 05 '12 at 18:09
  • i meant the less specific the compiler know of. Is that correct? – Santosh Gokak Nov 05 '12 at 18:36
  • Try to elaborate your answer...this is about precision (literally), so also some care in formulating is required (as you rightfully pointed out before). – Volker Stolz Nov 06 '12 at 11:21
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

Amit Deshpande
  • 19,001
  • 4
  • 46
  • 72
  • 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`. – Volker Stolz Nov 05 '12 at 17:52
  • @user133466 You can look at the generated bytecode using `javap`: http://docs.oracle.com/javase/1.5.0/docs/tooldocs/windows/javap.html – Volker Stolz Nov 05 '12 at 17:56