Wednesday, June 4, 2008

Collection

Is it necessary that the Comparator compare() method and equals() should have the same logic ?

7 comments:

RamrajChauhan said...

It is always advisable that if
a.equals(b) is true then a.compareTo(b) =0. I have not been able to figure out the reason for such a advice,but Java itself seems to contradict the above advice in case of BigDecimal.
In case of BigDecimal even though
2.0compareto(2.00) may return 0
but equals in the same case would return false,as it checks for both equal value as well as the scale.

So i guess though it is not mandatory to have same implentation,but it is advisable, unless your business demands.

-- i hope this helps.

Unknown said...

Comparator is use while comparing to object , and most of the time it is used when we want to sort the object insertion in our own way (accending or decending) like in TreeSet.
When we insert any object into set first it checks for equality whether object exist or not.
and now using comparator it decides where to put that object(as a left or right child node of tree).
So it like u define an object and depending on one property u decide whether they are equal and by same or other property u define ur comparator method.
So in case if the objects are equal set will never compare the objects as it is allready there in SET.

Saurabh said...

raveesh,

This is what i hv understood .. When we are inserting objects in a TreeSet.It checks for equality and then use the comparator to compare the two objects to put them in order.

if this what u hv said then i have a doubt ..

TreeSet uses TreeMap internally .When we call the add() method of TreeSet it calls put() method of TreeMap which calls compare or comapreto method .So the equals does not comes to picture when we are inserting or even getting the values.

am still not very clear why we shud :(

Tapan said...

import java.util.*;

public class Test implements Comparable{Test} {

int id = 0;

public Test(int i) {
this.id = i;
}

public static void main(String[] args) {
/* Trying to test Treeset (ts) by putting two objects which are equal
* according to equals() implementation but are not equal according
* to compareTo() implementation.
*
* And,
*
* Trying to test Treeset (ts1) by putting two objects which are unequal
* according to equals() implementation but are equal according
* to Comparator provided.
*
* The output is:
*
[t1, t2]: [Test@10b62c9, Test@82ba41]
How come we are here in equals()??
This can't be TreeSet which is calling. Let's read stack trace below:
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getStackTrace(Unknown Source)
Test.equals(Test.java:91)
Test.main(Test.java:66)

t1.equals(t2) : true
t1.compareTo(t2) : -10
ts: [Test@10b62c9, Test@82ba41]
ts.contains(new Test(30)): false

[obj1, obj2]: [java.lang.Object@130c19b, java.lang.Object@1f6a7b9]
obj1.equals(obj2) : false
ts1: [java.lang.Object@130c19b]
ts1.contains(new Object()): true

* Therefore, it seems that the TreeSet (ts) is accepting two equal object
* (according to equals()). But Set can't take duplicates and this implies
* that equality is not mandated by equals() for TreeSet (and the same implies for TreeMap).
* It is comapreTo() method which is deciding the equality of elements.
*
* The same case is with TreeSet (ts1). The two objects that we are putting
* are unequal according to equals() but are equal according to Comparator provided.
* And the TreeSet is denying obj2 because it is duplicate of obj1 as
* mandated by Comparator. Now when we check whether ts1 contains a given object:
* It always returns true because according to the Comparator provided to it,
* all objects are now equal. So again, equals() is not getting called.
*
*
* Therefore, for TreeSet (and for TreeMap also), the equality is decided by
* either the Comparator provided to it or Comparable implementation by the class
* of the elements.
*
* The equals() implementation never comes into picture for TreeSet/TreeMap.
*/
TreeSet{Test} ts = new TreeSet{Test}();
Test t1 = new Test(10);
Test t2 = new Test(20);
System.out.println("[t1, t2]: [" + t1 + ", " + t2 + "]");
System.out.println("t1.equals(t2) : " + t1.equals(t2));
System.out.println("t1.compareTo(t2) : " + t1.compareTo(t2));
ts.add(t1);
ts.add(t2);
System.out.println("ts: " + ts);
System.out.println("ts.contains(new Test(30)): " + ts.contains(new Test(30)) + "\n");

TreeSet{Object} ts1 = new TreeSet{Object}(new Comparator{Object}() {
public int compare(Object o1, Object o2) {
return 0;
}
});
Object obj1 = new Object();
Object obj2 = new Object();
ts1.add(obj1);
ts1.add(obj2);
System.out.println("[obj1, obj2]: [" + obj1 + ", " + obj2 + "]");
System.out.println("obj1.equals(obj2) : " + obj1.equals(obj2));
System.out.println("ts1: " + ts1);
System.out.println("ts1.contains(new Object()): " + ts1.contains(new Object()));
}

public boolean equals(Object obj) {
System.out.println("How come we are here in equals()?? " +
"\nThis can't be TreeSet which is calling. Let's read stack trace below:");
StackTraceElement[] st = Thread.currentThread().getStackTrace();
for(StackTraceElement ste : st) {
System.out.println(ste);
}
System.out.println();
return true;
}

public int compareTo(Test o) {
return (this.id - o.id);
}
}

Tapan said...

The code that I have posted will clear all doubts for you. I had to replace '<' with '{' while putting parameterized types since it doesn't accept html tags.

Saurabh said...

Tapan,

you are absolutely right that the equals() method is never called when we insert or retreive values from TreeSet or TreeMap.

So the qs remains why it is suggested that
a.equals(b) is true then a.compareTo(b) =0.

The case which ican think is if we are using TreeMap.
import java.util.TreeMap;

public class Test implements Comparable{Test} {

int id = 0;
String str = null;

public Test(String str) {
this.str = str;
}

public boolean equals(Object obj) {
if (obj instanceof Test) {
Test t = (Test) obj;
return (this.str).equalsIgnoreCase(t.str);
}
return false;
}

public int compareTo(Test obj) {

if (this.str.equals(obj.str)) {
return 0;
}
return 1;
}

public static void main(String args[]) {

TreeMap{Test, String} tm = new TreeMap{Test, String}();

Test t1 = new Test("SAURAbh");

tm.put(t1, "AGRA");

Test t2 = new Test("SAURABH");

if (t1.equals(t2)) {

String str = tm.get(t2);

boolean b = tm.containsKey(t2);

System.out.println(" str ----> " + str);

System.out.println(" boolean ----> " + b);

}

}

}

java Test

str ----> null
boolean ----> false


I think this is the reason why it is recommended.

PLease comment

Saurabh said...

But if we are inserting object in HashSet i still dont see a problem ...