3

I want to store data in a map, with key unicity, but I would like the map to use the equals method of my key class.

It seems that HashMap doesn't use the equals method (I may be wrong, if so my tests are wrong).

My problem here is that the map use hashCode to check for duplicate, and I would like a map implementation that use equals.

I am storing timestamp in the key, and would like to make it so that 2 keys are equals if there timestamp difference does not exceed a defined amount (let say 1000 ms).

Edit : code

public class CleanKey
{
    private DateTime start;
    private DateTime end;

    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((end == null) ? 0 : end.hashCode());
        result = prime * result + ((start == null) ? 0 : start.hashCode());
        return result;
    }

    public boolean equals(Object obj)
    {
        if(this == obj)
            return true;
        if(obj == null)
            return false;
        if(getClass() != obj.getClass())
            return false;
        CleanKey other = (CleanKey) obj;
        if(end == null)
        {
            if(other.end != null)
                return false;
        }
        else if(Math.abs(Millis.millisBetween(end, other.end).getMillis()) > 1000)
            return false;
        if(start == null)
        {
            if(other.start != null)
                return false;
        }
        else if(Math.abs(Millis.millisBetween(start, other.start).getMillis()) > 1000)
            return false;
        return true;
    }
}
iXô
  • 1,133
  • 1
  • 16
  • 39
  • 2
    Your tests are wrong. HashMap *does* use equals - after it uses hashCode. Chances are your hashCode method is inconsistent with equals... or some other error in your code that we can't see. Post your code, we'll show you the problem. – Jon Skeet Aug 06 '15 at 16:03
  • I have added explanation of my problem. Can I return a constant value for hashCode to force the use of equals ? – iXô Aug 06 '15 at 16:04
  • 1
    Code would have been better than just a description. Yes, you can use a constant hash code (which will kill the performance) but you can't implement the equality test you want without violating the contract of `equals`. – Jon Skeet Aug 06 '15 at 16:08
  • Now you've posted code, which is good - but it also contains a lot of *extraneous* detail. You should always post a short but complete example which does *nothing* but demonstrate the problem. – Jon Skeet Aug 06 '15 at 16:09
  • This is the simplest code I can provide, it show the full hashcode and full equals methods. There is nothing else. – iXô Aug 06 '15 at 16:12
  • No, this is absolutely *not* the simplest code you could provide. Did you have to use a WeakReference? No. Did you have to have multiple other parts of the key? No. Did the other parts of the key have to be reference types? No. A simple key with just, say `id` (an `int`) and the timestamp as a `long` would have been *much* simpler, without removing anything relevant to your question. It's really important to be able to isolate what's actually important about a question. (Your `equals` method is currently 78 lines. It could have been about 8.) – Jon Skeet Aug 06 '15 at 16:16
  • edited question to reflect your request. I usually don't clean to much my case in case it hides a defect elsewhere – iXô Aug 06 '15 at 16:22
  • That's still way more code than it needs to be. And so long as the "clean" code still demonstrates the problem, it's still useful for the question. – Jon Skeet Aug 06 '15 at 16:26
  • I first I didn't provide code sample, because I thought my question was clear enough. Now I am providing to much information, what would I have to remove to make it "good" ? – iXô Aug 06 '15 at 16:27

2 Answers2

12

It seems that HashMap doesn't use the equals method (I may be wrong, if so my tests are wrong).

It does use equals, but it uses hashCode first. It will only bother calling equals on keys with the same hash code - that's how it manages to be efficient. That's not a problem so long as your hashCode and equals method obey the contract specified in java.lang.Object.

I am storing timestamp in the key, and would like to make it so that 2 keys are equals if there timestamp difference does not exceed a defined amount (let say 1000 ms).

You can't do that. It violates the contract of equals, because you can't have transitivity. Suppose we have three keys x, y, and z with the following timestamps:

x    400
y   1200
z   2000

By your description, x.equals(y) would be true, y.equals(z) would be true, but x.equals(z) would be false, thus violating the contract of Object.equals.

The equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.
Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • The only purpose of my key class it so eliminate 'close' timestamp values, returning a fixed value (always the same) for hashCode seems to do the trick. Map is using equals, and I can eliminate duplicates. I know it is not a good way to do it. But how can I do it ? – iXô Aug 06 '15 at 16:11
  • @iXô: If you're not going to use the hash code, why use a `HashMap` at all? Fundamentally the Java collection classes are designed to work with the contract of equals - currently your behaviour would be hard to predict. For example, imagine that you had the three values x, y and z to add into the map - it could end up with just y, or x and z... how would you know which to use? Unfortunately we don't have enough information on what you're trying to achieve, which makes it hard to suggest a solution... but the solution to your actual *question* is "HashMap already uses equals"> – Jon Skeet Aug 06 '15 at 16:14
  • That's why my first question was : is there a map that use equals. Something like a EqualsMap instead of HashMap ;) I search in apache collection, it seems that this kind of strange map doesn't exists. – iXô Aug 06 '15 at 16:16
  • @iXô: And as I said, the answer is that HashMap *does* use equals. It uses hashCode as well though. – Jon Skeet Aug 06 '15 at 16:18
  • @iXô You really wouldn't want a `Map` that uses `equals` without using `hashCode` first. It would be much slower. Also, there's no point. It's getting `equals` right that's the hard bit - `hashCode` is really easy. – Paul Boddington Aug 06 '15 at 16:20
  • Equals is generally only used to resolve conflicts that arise as a result of a collision. By forcing all of your objects to return the same constant hashcode, you are essentially creating a HashMap that contains a list, and every time you put something into or retrieve something from the map you are forcing a linear search through that list that calls equals on every object already in the map (until it finds true or gets to the end of the list). You would actually be more efficient using a list and searching it yourself as you'd eliminate the overhead of the hashing. – Bobby StJacques Aug 06 '15 at 16:20
  • I wanted to filter data. Let says I have 2 list of something, the 2 lists may contains what I call duplicates (because of the timestamps beeing to close) what should I do to have a new list where I don't have my duplicates ? – iXô Aug 06 '15 at 16:24
  • I'm only suggesting that what you are doing is wrapping a list up inside of a HashMap, and asking the HashMap to search the list for you and call "equals" on every object in the map. You might as well just create your own list and search it yourself in a for loop (this would actually be slightly more efficient). Have you considered dropping the least significant digits from your time stamps, or rounding? – Bobby StJacques Aug 06 '15 at 16:33
  • Perhaps the correct title for this question would have been: "Does a map using ONLY equals method for key checking exists?" Not disagreeing with all the good info and advice given in the answers and comment, I too think that such a map would be pretty bad and braking the equals contract is also bad but really the question author was searching for the answer if there exists a map that ONLY uses equal(). – nyholku Oct 16 '17 at 06:14
0

You need to override hashCode and equals in you class.

Here: Understanding the workings of equals and hashCode in a HashMap

Edit after seeing the code :

Hashcode is returning wrong value because you are using end field to calculate the hash... different end lead to different hash.

Just for a try... return a constant and the hashmap will work

Community
  • 1
  • 1
Jkike
  • 807
  • 6
  • 12
  • That what I did, but using debugger show that when using put on the hashmap, it use the hashCode method and not equals. And because I need specific strange equals method it fails to detect duplicates – iXô Aug 06 '15 at 16:01