Created a static nested class which implements Comparable, and override Object.equals such that e1.compareTo(e2)==0 and e1.equals(e2)==true are not synonymous.
Then i add the objects into TreeSet and HashSet respectively using its add method.
I expected that insertion of multiple such objects into either a TreeSet or a HashSet will succeed, since both claim reliance on equals to determine uniqueness but i found inserting multiple such objects into a TreeSet will fail while inserting them into a HashSet will succeed.
public class Test {
/*
* This inner class deliberately has a compareTo method that is not
* consistent with equals
*/
static class TestObject implements Comparable<TestObject> {
#Override
public int compareTo(TestObject arg0) {
// No two of these objects can be ordered
return 0;
}
#Override
public boolean equals(Object arg0) {
// No two of these objects are ever equal to each other
return false;
}
}
public static void printSuccess(boolean success) {
if (success)
System.out.println(" Success");
else
System.out.println(" Failure");
}
public static void main(String[] args) {
TreeSet<TestObject> testTreeSet = new TreeSet<TestObject>();
HashSet<TestObject> testHashSet = new HashSet<TestObject>();
System.out.println("Adding to the HashSet:");
printSuccess(testHashSet.add(new TestObject()));
printSuccess(testHashSet.add(new TestObject()));
printSuccess(testHashSet.add(new TestObject()));
System.out.println("Copying to the TreeSet:");
for (TestObject to : testHashSet) {
printSuccess(testTreeSet.add(to));
}
}
}
Output of above program is
Adding to the HashSet:
Success
Success
Success
Copying to the TreeSet:
Success
Failure
Failure
Can some one tell me why Tree set is behaving like this ?
"a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal". https://docs.oracle.com/javase/7/docs/api/java/util/TreeSet.html
And your compareTo says they are all equal.
The return value 0 for compareTo means the objects are equal, so e1.compareTo(e2) == 0 if and only if e1.equals(e2) == true.
TreeSet guarantees oredering so it uses the compare method, and HashSet does not so it uses the equals method. Try changing the compareTo method to a positive/negative number instead.
You can read more about the Compareable interface here.
Also the javadoc of java.util.Comparator explicitly describes this case and tells that Comparator for SortedSet must be consistent with equals:
For example, suppose one adds two elements a and b such that
(a.equals(b) && c.compare(a, b) != 0) to an empty TreeSet with
comparator c. The second add operation will return true (and the size
of the tree set will increase) because a and b are not equivalent from
the tree set's perspective, even though this is contrary to the
specification of the Set.add method.
Related
I'm trying to use HashSet, while using my own class "Inner" as key type, as below:
import java.util.HashSet;
class Inner {
int i;
String s;
public Inner(int i, String s) {
this.i = i;
this.s = s;
}
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public boolean equals(Object o) {
Inner inner = (Inner) o;
return i == inner.i && s.equals(inner.s);
}
}
public class testEquals {
public static void main(String [] args) {
HashSet<Inner> hi = new HashSet<>();
hi.add(new Inner(1,"abc"));
System.out.println(hi.contains(new Inner(1,"abc")));
}
}
It prints "false"
(1) My question is, as long as I try to use "contains" function, I have to construct a new object from "Inner" class to query, but because it's a new object, hashcode() is different. So I always get a "false" for "contains" functions.
(2) If I change the hashCode() to return "true" like equals when values are the same, then in other scenarios, different objects references are considered as "==" just like one unique reference.
(1) and (2) seems to conflict.
How to solve this?
Thanks!
You should override hashCode in a way that two equal objects will have the same hashCode.
For example:
#Override
public int hashCode() {
return Objects.hash(i,s);
}
I'm not sure what's your problem with (2). If two objects are equal according to equals(), the HashSet should treat them as identical objects, even if they are not.
If, one the other hand, you wish the HashSet to treat any Inner instance as unique (regardless of the values of its instance variables), simply don't override hashCode and equals. However, it is rarely useful to use HashSet without overriding these methods.
You have to overide hashCode() correctly without breaking the equals - hashcode contract.
The contract between equals() and hashCode() is:
If two objects are equal, then they must have the same hash code.
If two objects have the same hash code, they may or may not be equal.
hashCode() does not returns true, it returns an int value. So just make sure it returns same int value for 2 different objects whose equals() return true.
Hello I probably oversaw something, but here it goes.
I have a TreeSet<CustomObject> and I do not want to have duplicates in the Set. My CustomObject class looks like this.
class CustomObject implements Comparable<CustomObject> {
String product;
String ean;
public CustomObject(String ean){
this.ean = ean;
// product is getting set via setter and can be null
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CustomObject that = (CustomObject) o;
return ean.equals(that.ean);
}
#Override
public int hashCode() {
return ean.hashCode();
}
#Override
public int compareTo(CustomObject another) {
if(equals(another)) return 0;
if(product != null && another.product == null) return -1;
if(product == null) return 1;
return product.compareToIgnoreCase(another.product);
}
}
Now I have a add Function for new objects.
private final TreeSet<CustomObject> productCatalog;
public void addObject(SomeData tag) {
CustomObject p = new CustomObject(tag.getEan());
if (productCatalog.contains(p)) { // <-- This checks only one entry of the Set.
for (CustomObject temp : productCatalog) {
if (temp.equals(p)) {
p = temp; // I do stuff with that later which is irrelevent here
}
}
} else {
productCatalog.add(p);
}
}
The method productCatalog.contains(p) calls the compareTo method from the Comparable Interface and does the comparing. The issue here is that it literally only checks I think the last object? in the set. So what happens is that only one unique CustomObject entry is present.
This is the scenario when I follow it with the debugger:
productCatalog.contains(p)
calls compareTo
calls equals to check if ean.equals(that.ean)
returns once true, but every other time false. Since it only checks the last object
How can I have it to check not only one object in step 4, but all the present objects in the Set. What am I missing?
Thx!
EDIT: These are some sample data. For the simplicity SomeData tag is basically a String.
First run:
addObject("Ean1") // success added
addObject("Ean2") // success added
addObject("Ean3") // success added
addObject("Ean4") // success added
Everything gets added into the TreeSet.
Second run:
addObject("Ean1") // failed already in the map
addObject("Ean2") // failed already in the map
addObject("Ean3") // failed already in the map
addObject("Ean5") // success added
addObject("Ean4") // success added
addObject("Ean4") // success added
For testing purpose I manually set product names depending on the String ean.
public CustomObject(String ean){
this.ean = ean;
switch(ean){
case "Ean1": product = "TestProduct"; break;
case "Ean2": product = "ProductTest";break;
case "Ean3": product = "Product";break;
}
The TreeSet acts as a cache.
Edit2: This is how I solved it.
for (CustomObject temp : productCatalog) {
if (temp.equals(p)) {
p = temp; // I do stuff with that later which is irrelevent here
}
}
I removed the if statement with the contains method since that would always return ยด1or-1in my special case. Now I simply iterate over the Set to correctly use theequals` method since the TreeSet uses compareTo() for checking every element in the Set.
The Java Docs state the following
Note that the ordering maintained by a set (whether or not an explicit
comparator is provided) must be consistent with equals if it is to
correctly implement the Set interface. (See Comparable or Comparator
for a precise definition of consistent with equals.) This is so
because the Set interface is defined in terms of the equals operation,
but a TreeSet instance performs all element comparisons using its
compareTo (or compare) method, so two elements that are deemed equal
by this method are, from the standpoint of the set, equal. The
behavior of a set is well-defined even if its ordering is inconsistent
with equals; it just fails to obey the general contract of the Set
interface.
The main problem:
compareTo does return 1 if both product and other.product are null. This is wrong because they are actually equal. You probably forgot to set product names for the higher ean values, like "Ean4" and "Ean5".
Old answer:
Your implementations of equals and compareTo do not fit together.
equals works on the ean and compareTo on the product. This only works if you implicitly assume that equal ean imply equal product. If this is not true in your test cases, the result will be wrong.
In either case, it is no good implementation because this can lead to a < b, b < c but a "equals" c.
This question already has answers here:
When does HashSet 'add' method calls equals? [duplicate]
(4 answers)
Closed 4 years ago.
here is my code :
public class testGui {
public static void main(String[] arg){
class TESTS{
String t;
public TESTS(String t){
this.t = t;
}
#Override
public boolean equals(Object x){
System.out.println("My method is called...");
if(x instanceof TESTS){
TESTS zzz = (TESTS) x;
return zzz.t.compareTo(t)==0;
}
else return false;
}
}
HashSet<TESTS> allItems = new HashSet<TESTS>();
allItems.add(new TESTS("a"));
allItems.add(new TESTS("a"));
System.out.println(allItems.contains(new TESTS("a")));
}
}
I do not get why the hashset contains method is not calling my equals method as mentionned in their specifications :
More formally, adds the specified
element, o, to this set if this set
contains no element e such that
(o==null ? e==null : o.equals(e))
My code is returning false and not going into my equals method.
Thanks a lot for answering!
When you override equals, you must also override hashCode. Otherwise, equal objects will have different hash codes and be considered unequal.
It is also strongly recommended not to override only hashCode. But this is not essential, as unequal objects can have the same hash code.
The HashSet depends on the HashCode of each object. Before the equals method is called, the hashCode method will be called. If hashcodes are equal, then the hashset deems it worthy of evaluating the equals method.
Implement a hashcode method such that if a.equals(b) == true, then a.hashCode() == b.hashCode()
and it should start working as you would expect.
You should also implement hashCode, so that it is consistent with equals. HashSet uses the hashCode method to decide which bucket to put an item into, and calls equals only when the hash code of two items are the same.
Effective Java, 2nd Edition discusses this rule (and the consequences of breaking it) in Item 9: Always override hashCode when you override equals.
As most of the comments have been... just override the hashcode method (sample below) and you should be good.
#Override
public int hashCode() {
return t.hashCode()*31;
}
I was trying to answer this question in the forum and I found that despite of overriding the equals method in the Employee class, I am still able to add duplicate elements to the TreeSet.
The Javadoc of TreeSet.add(E) method says
Adds the specified element to this set if it is not already present.
More formally, adds the specified element e to this set if the set
contains no element e2 such that (e==null ? e2==null : e.equals(e2)).
If this set already contains the element, the call leaves the set
unchanged and returns false.
This essentially means that no 2 equals objects will be inserted into TreeSet and equality is determined solely by equals() method of contained objects.
However, below code is adding 2 elements to the Set even though they are equal
public class Employee implements Comparable<Employee> {
String employeeName;
int employeeId;
public Employee(String name, int id) {
this.employeeName = name;
this.employeeId = id;
}
public int compareTo(Employee emp) {
//return this.employeeName.compareTo(emp.employeeName);
return (this.employeeId - emp.employeeId);
}
#Override
public String toString() {
return ("Name is: " + employeeName + " Emp id is: " + employeeId);
}
#Override
public boolean equals(Object emp) {
if(emp instanceof Employee &&((Employee)emp).employeeName.equals(this.employeeName)){
return true;
}
return false;
}
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Set<Employee> set = new TreeSet<Employee>();
Employee e1 = new Employee("A", 1);
Employee e2 = new Employee("A", 2);
System.out.println(e1.equals(e2));
set.add(e1);
set.add(e2);
System.out.println(set);
}
}
And here is the output
true
[Name is: A Emp id is: 1, Name is: A Emp id is: 2]
Why is TreeSet allowing multiple elements even if they are equal?
Now I changed the compareTo method of Employee like this
public int compareTo(Employee emp) {
return this.employeeName.compareTo(emp.employeeName);
}
And the output is
true
[Name is: A Emp id is: 1]
How the TreeSet working properly after overriding compareTo ?
The javadoc of TreeSet also says:
Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
I agree that the javadoc of add() could make it clearer that equals() is not actually used, and re-state or link to this warning, though.
Set implementations use a Map behind the scenes to manage the data and, in doing so, guarantee the uniqueness of an object through the Map's key.
As far as I know, no explicit test of equals is done meaning that your object would be added if it were not explicitly the same object (i.e. '==').
I think the docs are misleading but I guess that it's saying it's the formal language representation. It's ambiguous so I Could be wrong but I'm pretty sure that uniqueness is guaranteed by virtue of the underlying map's keys. FYI: A dummy value is passed with your object as the key.
First thing about TreeSet is, it also maintain order of object while stroing them and use compareTo method.
Because in your first version of compareTo method you are using employeeId for comparison and put diffrent emoplyee id for object e1 and e2.
Treeset will treat this as a different object as comparison doesn't send zero value (it will send either + or - value).
but in second verion you implement compareTo using employeeName, which is same for object e1 and e2.
Consider the code:
class A {
private int i;
boolean equals( Object t) {
if (this == t)
return true;
if (!( t instanceof A))
return false;
if (this.i == t.i);
}
}
Map<String,A> orig;
Map<String,B> dup;
I am trying to do this
orig.entrySet().removeAll(dup.entrySet());
I see that the equals method is called; is this always true, or might it call compareTo instead?
Yes, it calls equals(). compareTo() could only be used if the Set knew that it contained Comparable objects (sorted sets, for instance, might possibly do this).
It depends on the implementation.
For instance, a HashSet will use hashCode and equals. A TreeSet will probably use compareTo. Ultimately, so long as your types behave appropriately it shouldn't matter.
The TreeSet uses the compareTo, try this:
public class A {
private int i;
A(int i) {
this.i = i;
}
#Override
public boolean equals(Object t) {
if (this == t)
return true;
if (!( t instanceof A))
return false;
return (this.i == ((A)t).i);
}
public static void main(String[] args) {
List<A> remove = Arrays.asList(new A(123), new A(789));
Set<A> set = new TreeSet<A>(new Comparator<A>() {
#Override
public int compare(A o1, A o2) {
return o1.i - o2.i;
// return 0; // everything get removed
}
});
set.add(new A(123));
set.add(new A(456));
set.add(new A(789));
set.add(new A(999));
set.removeAll(remove);
for (A a : set) {
System.out.println(a.i);
}
System.out.println("done");
}
}
make the Comparator always return 0 and everything will be removed! Same happens if not using a Comparator but implementing Comparable.
The TreeSet is based on a TreeMap which uses the compareTo in getEntry.
In the Javadoc of the TreeSet you can (finally) read:
...the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method...
[]]
http://java.sun.com/j2se/1.5.0/docs/api/java/util/Collection.html
"Implementations are free to implement optimizations whereby the equals invocation is avoided, for example, by first comparing the hash codes of the two elements."
Most likely will use equals, but considering the statement above, you cannot fully rely on equals() to be called. Remember that it's always a good idea to override hashCode() whenever you override equals().
Some Set implementations rely on hashCode (e.g. HashSet). That is why you should always override hashCode too when you override equals.
The only implementation within the Java library that I am aware of that wont do this is IdentityHashMap. TreeMap for instance does not have an appropriate Comparator.
I don't see where compareTo is used; the javadoc for remove() for the Map interface says "More formally, if this map contains a mapping from key k to value v such that (key==null ? k==null : key.equals(k)), that mapping is removed." While for the Set interface it similarly says "More formally, removes an element e such that (o==null ? e==null : o.equals(e)), if the set contains such an element."
Note that removeAll()'s javadoc doesn't say how it operates, which means, as others have said, that it's an implementation detail.
In Sun's Java, according to Bloch in his Effective Java (if I remember correctly), it iterates over the collection and calls remove(), but he stresses that you must never assume that's how it's always done.