using HashSet with overridden .equals() [duplicate] - java

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;
}

Related

Is it recommended to use hashcode to determine equality in Java? [duplicate]

This question already has answers here:
Java: Use hashCode() inside of equals() for convenience?
(7 answers)
Closed 7 years ago.
Let's say we have a hashcode() function, which will then be used inside our equals() method to determine the equality of two objects. Is this an allowed/accepted approach?
Assume that we use a simple implementation of a hash code. (For example a few instance variables multiplied by prime numbers.)
This is a terrible way to check for equality, mostly since Objects don't have to be equal to return the same hashcode.
You should always use the equals method for this.
The general rule is:
If the equals method returns true for Objects a and b, the hashCode
method must return the same value for a and b.
This does not mean, that if the hashCode method for a and b returns
the same value, the equals method has to return true for these two
instances.
for instance:
public int hashCode(){
return 5;
}
is a valid, though be it inefficiƫnt, hashcode implementation.
EDIT:
to use it within an equals method would be something like this:
public class Person{
private String name;
public Person(String name){ this.name = name;}
public String getName(){ return this.name;}
#Override
public boolean equals(Object o){
if ( !(o instanceof Person)){ return false;}
Person p = (Person)o;
boolean nameE = this.name == null ? p.getName() == null : this.name.equals(p.getName());
boolean hashE = nameE ? true : randomTrueOrFalse();
// the only moment you're sure hashE is true, is if the previous check returns true.
// in any other case, it doesn't matter whether they are equal or not, since the nameCheck returns false, so in best case, it's redundant
return nameE && hashE;
}
#Override
public int hashCode(){
int hash = generateValidHashCode();
return hash;
}
}
It is a very bad practice. Hashes are supposed to have a minimal amount of collisions, but usually you have more possibilities for objects than the amount of possible hashes and because of the pigeonhole principle a few distinct objects must have the same hash.
When comparing hashes, you have a certain chance of getting "false positives".
Actually, it is not a bad idea!
But make sure you use this method to determine inequality, not equality. Hashing code may be faster than checking equality, especially when hashcode is stored (for example in java.lang.String).
If two object have different hashcodes they must be different, else they may be the same. For example you may use this method as the following
Object a, b;
if(a.hashCode() == b.hashCode()){
if(a.equals(b)) return true;
}
return false;
Be aware that in some cases code above may be slower than using only equals(), especially when in most cases a does equal b.
From documentation of Object.java:
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.
Don't do this
While it is correct that you need to override equals() and hashCode() in pairs, having the same hash is not the same as having the same values.
Put some effort into really thinking the equality thing through. Don't shortcut here it will bite you later.

HashSet equality called with passed object instead of stored item

In the following code, the output shows CONTAINS for each object, whereas commenting out the anonymous object's equals() method results in MISSING, which leads me to believe the second equality pass (hashCode() -> equals()) actually calls the equality method of the supplied object instead of the object within the collection being tested.
List<String> strings = Arrays.asList("Hello", "there", "Qix");
HashSet<String> set = new HashSet<>(strings);
for(final String s : strings)
{
boolean contains = set.contains(new Object(){
#Override
public int hashCode() {
return s.hashCode();
}
#Override
public boolean equals(Object obj) {
return true;
}
});
System.out.format("%s: %s\n",
s,
contains ? "CONTAINS" : "MISSING");
}
Why is this? Is it because the equals() method, by principle, should be symmetric between the two objects?
The HashSet either has to do a.equals(b) or b.equals(a). And as they should be written to be symmetric*, it shouldn't matter which it chooses.
But for reference, the documentation states:
returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e))
* See http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#equals(java.lang.Object).
It's an implementation detail which we shouldnt worry about since it's never said in public API how it uses equals. Like you said it's supposed to be symmetric anyway. If we go into src we'll see that it is really passedObject.equals(storedObject)
Becaue AFAIK the default Object.equals implementation uses reference comparison so it checks if the two references refer to the same object.
Here you are comparing instances of Strings with another custom classe, they can't be equal, whatever the way this is done.

How to find difference between two arraylist in java

I have two arraylist with a number of model objects.I want to find the difference of these arraylists.When I use strings instead of models, I got the difference with removeall function in collection framework. But for model objects it doesnot work. Please any one help me
Implement equals and hashCode in your custom object and you can use the same approach as you did with Strings.
Well, the removeAll method is a generic library method which doesn't know anything about your model class. So if you think about it for a second, how is it going to know which ones are "the same"?
The short answer is that you need to override the equals() method in your Model class, as this is what the checks are based on. The implementation should return true for any pair of model instances that you wish to be considered the same - the default inherited behaviour returns true only if they're the same object in memory. (And as always, when you override equals() you must override hashCode() too).
String class has already overridden version of equals and hashCode method so you are able to use remove() method. If you have to use your class in collection (List or Set) then you will have to override these methods in your class otherwise it will use default implementation of these methods.
If two objects are logically equal that means their hashCode must be equal as well as they satisfy equals().
For comparing two ArraList you need two compare two objects.In your case it is your model object,for that you need to override equals method.
Try this code
#Override
public boolean equals(Object compareObj)
{
if (this == compareObj)
return true;
if (compareObj == null)
return false;
if (!(compareObj instanceof MyModel))
return false;
MyModel model = (MyModel)compareObj;
return this.name.equals(model.name); // Are they equal?
}
#Override
public int hashCode()
{
int primeNumber = 31;
return primeNumber + this.name.hashCode();
return 0;
}

contains() method in java.util.HashSet doesn't behave as i expected from it

This is the java main() method:
public static void main(String[] args) {
HashSet set = new HashSet();
Mapper test = new Mapper("asd", 0);
set.add(test);
System.out.println(new Mapper("asd", 0).equals(test));
System.out.println(set.contains(new Mapper("asd", 0)));
}
and my Mapper class is :
class Mapper {
String word;
Integer counter;
Mapper (String word, Integer counter) {
this.word = word;
this.counter = counter;
}
public boolean equals(Object o) {
if ((o instanceof Mapper) && (((Mapper)o).word == this.word)) {
return true;
}
return false;
}
}
and the result is :
true
false
From HashSet specifications, at this method I read this : "Returns true if this set contains the specified element. More formally, returns true if and only if this set contains an element e such that (o==null ? e==null : o.equals(e)). "
So, can anyone explain me where i'm wrong? Or ...?
Thanks.
You need to implement a proper hashCode() function.
public int hashCode() {
// equal items should return the same hashcode
}
The Java utilites java.util contain a lot of classes which rely on hashing. To allow one to override equals() as they see fit means that one must also properly override hashCode() to match.
A correct implementation of hashCode() would return the same hash for any two objects where equals() returns true. Hash related functions check the hashes for equality before checking to see if the objects equal (to resolve hash collisions).
The hashCode contract says that if two objects are equal, they should have the same hash code. Collections like HashSet assume that this is upheld.
Object's implementations of equals and hashCode are based on addresses (or object IDs). If you override equals to do a content comparison, you should override hashCode to generate a hash based on content.

Which method does Set.removeAll() use underneath: equals or compareTo?

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.

Categories

Resources