11

I did this test in a HashSet comparision and equals is not being called

I would like to consider equals when farAway=false (A function to check two point distances)

Full compilable code, you could test it, and tells why equals is not being called in this example.

public class TestClass{
     static class Posicion
    {
        private int x;
        private int y;

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final Posicion other = (Posicion) obj;
            if ( farAway(this.x, other.x, this.y, other.y,5)){   
                return false;
            } 
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 7; hash = 59 * hash + this.x; hash = 59 * hash + this.y;
            return hash;
        }

         Posicion(int x0, int y0) {
            x=x0;
            y=y0;
        }

        private boolean farAway(int x, int x0, int y, int y0, int i) {
            return false;
        }
    }

    public static void main(String[] args) {
        HashSet<Posicion> test=new HashSet<>();
        System.out.println("result:"+test.add(new Posicion(1,1)));
        System.out.println("result:"+test.add(new Posicion(1,2)));
    }
}

EDIT

-Is there a way to force HashSet add to call equals?

Hernán Eche
  • 6,529
  • 12
  • 51
  • 76
  • what is that `farAway` invocation inside `equals`?? your class si breaking the contract between `hashCode` and `equals`! – Alonso Dominguez Jan 24 '13 at 15:37
  • @AlonsoDominguez There you have a complete test, farAway just return false, to ensure object to be treated as equals, but equals is never called. – Hernán Eche Jan 24 '13 at 15:42
  • you must be compliant with contract between `hashCode` and `equals`. That means that when your `equals` method returns true then you `hashCode` must return the same integer value. And that's not happening in your code. – Alonso Dominguez Jan 24 '13 at 15:44
  • @AlonsoDominguez I understand that, I was trying to **avoid** hashCode, and use only equals, I see now that is not possible, thanks for insist with *contract* break, then now I know when equals *is not* called, I still don't know when does equals *is actually called*. – Hernán Eche Jan 24 '13 at 15:50
  • 1
    @HernánEche: what you seem to be looking to do can be very tricky - not only do you have to adhere to `hashCode()`'s contract, but also the `equals()` contract. `equals()` has to be transitive. So if you return `true` for A and B because they are close to each other and B and C because they are close to each other, you will run into problems when checking for equality between A and C if they happen to not be close. I think you'll need to devise a container/data structure other than the standard HashSet to do what you want to do (if what you want to do is even a good idea in the first place). – Michael Burr Jan 24 '13 at 19:30
  • @MichaelBurr Bold insight +1 (and funny parenthesis), good point, euclidian distance will not be transitive, putting any threshold, A could be 'near' B, and B 'near' C, but we can't say anything about C in respect to A, it could be near or not, then "equals" should compare with all elements (something it doesn't). I was aware of that, it doesn't break my code because here is an order in loading, and it's not euclidean distance, and but it's a very useful comment, thanks – Hernán Eche Jan 24 '13 at 21:26

4 Answers4

31

If the hash codes differ, there is no need to call equals() since it is guaranteed to return false.

This follows from the general contract on equals() and hashCode():

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.

Right now your class is breaking that contract. You need to fix that.

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • I need equals to be called – Hernán Eche Jan 24 '13 at 15:31
  • Then the hashcode shouldn't be the same. – RoflcoptrException Jan 24 '13 at 15:31
  • That's the question about, hashCode is not the same, and equals is not being called. – Hernán Eche Jan 24 '13 at 15:32
  • @HernánEche Do you already have an item in your set? If you add the first one, `equals` may not be called (since there's nothing to compare it with). – Sebastian Krysmanski Jan 24 '13 at 15:34
  • 4
    @HernánEche: it makes no sense. HashSet calls equals() to ensure that duplicate objects aren't stored in the set. If it can ensure that without calling equals (for example, because there is nothing in the set yet, or because no other object in the set has the same hash code), why would it call equals()? And with wich other object? – JB Nizet Jan 24 '13 at 15:36
  • @SebastianKrysmanski yes, of course, you could test the example. – Hernán Eche Jan 24 '13 at 15:52
  • 1
    @HernánEche:the two objects return different hashcodes. By the contract for `hashCode()` the library (or anyone) can take that to mean the objects are not equal - there's never a need to call `equals()` if the hashcodes are different. – Michael Burr Jan 24 '13 at 15:58
  • @MichaelBurr The point is that if hashcode are different then no need to call equals, and if hashcode are equals then no need to call equals, finally equals is never called, so the answer to this question should be, **equals is never called, no matter what you put inside it or inside hashCode** – Hernán Eche Jan 24 '13 at 16:01
  • @HernánEche: That's not true. Make `hashCode()` always return `0` and see what happens. – NPE Jan 24 '13 at 16:02
  • 7
    @Hernán: If the hash codes are different, the objects are different and equals is not invoked. If the hash codes are identical, the objects *may* be equal, hence equals(Object) is called to ensure that they are the same. – jarnbjo Jan 24 '13 at 16:04
  • @jarnbjo you are right, I test it wrong, thanks! – Hernán Eche Jan 24 '13 at 16:09
12

If you want equals() to be called always, just always return, say, 0 in hashCode(). This way all items have the same hash code and are compared purely with equals().

public int hashCode() {
  return 0;
}
Sebastian Krysmanski
  • 8,114
  • 10
  • 49
  • 91
  • Then could you please explain what result you're expecting? What output should your code produce? – Sebastian Krysmanski Jan 24 '13 at 16:02
  • Functionality is reject add two objects that are similar, in this case two points in the plane that are close each other, but I can reach same functionality in a hundreds ways, the question is not about solving a single problem, but about to understand **when** equals is actually called by HashMap, or if equals is never called through it. – Hernán Eche Jan 24 '13 at 16:06
  • I've just tested my code and it works as expected. The call `test.add(new Posicion(1,2)` returns `false` because `equals()` is called. What's wrong with this? – Sebastian Krysmanski Jan 24 '13 at 16:09
  • My test was wrong, all clear now, thanks – Hernán Eche Jan 24 '13 at 16:11
  • Are there some drawback using this trick? – optimusfrenk Mar 19 '15 at 09:53
  • making all object go into the same bucket will make the search very slow my friend – Van Teo Le Jul 20 '23 at 09:05
1

It sounds like HashSet isn't right for you. It sounds like you want a custom way of comparing two positions. Rather than saying "are two positions exactly equal?". Instead, you should look at using TreeSet, with a Comparator. This way, you can write a "IsWithinRangeComparator" and do your range checking there.

David Lavender
  • 8,021
  • 3
  • 35
  • 55
-1

As suggested above,when objects are equal, their hashcode should also be the same. You could make a simple fix to your hashcode computation like below.

 public int hashCode() {

int hash = 7; hash = 59 * hash + this.x; hash = 59 * hash + this.y;
boolean faraway=farAway(this.x, other.x, this.y, other.y,5);
hash=59*hash+(faraway?1:0); //include faraway also as part of hashcode computation

 return hash;

}

Zenil
  • 1,491
  • 3
  • 12
  • 21
  • 1
    It is of course not possible to use the farAway method from hashCode(), since you don't have the other object to compare with. – jarnbjo Jan 24 '13 at 15:48
  • 1
    @herman : Hashet works by comparing the object's hashcode. If the hashcode's are different, then there is no need to call equals() because the object's are considered different and both objects will be put in the hashset. equals() is called only if the hashcodes are same and in that case equals() will be called to compare the values – Zenil Jan 24 '13 at 15:55
  • @Zenil no, if hashcodes are same, equals won't be called, just because it means object are the same!, then I still wonder, when does equals is being called? – Hernán Eche Jan 24 '13 at 15:57
  • @herman,@jarnbbjo : faraway() takes 5 arguments. Why do you need the other object to compare with ? You need the other objects' x and y which is passed in. faraway() can be called in hashcode just like it is being called in equals()..Am I missing something ? – Zenil Jan 24 '13 at 15:57
  • 1
    @Zenil: Yes, you are missing something. Where do you want to take the arguments other.x and other.y from if you don't have the other object? – jarnbjo Jan 24 '13 at 16:11