2

I have a class Reminder that has both hashcode and equals overridden like this:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((cronExpression == null) ? 0 : cronExpression.hashCode());
    result = prime * result + ((subject == null) ? 0 : subject.hashCode());
    result = prime * result + timeout;
    result = prime * result + ((type == null) ? 0 : type.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (!(obj instanceof Reminder))
        return false;
    Reminder other = (Reminder) obj;
    if (cronExpression == null) {
        if (other.cronExpression != null)
            return false;
    } else if (!cronExpression.equals(other.cronExpression))
        return false;
    if (subject == null) {
        if (other.subject != null)
            return false;
    } else if (!subject.equals(other.subject))
        return false;
    if (timeout != other.timeout)
        return false;
    if (type == null) {
        if (other.type != null)
            return false;
    } else if (!type.equals(other.type))
        return false;
    return true;
}

Both overrides were automatically generated using Eclipse. I'm using the Reminder in a HashSet instantiated like this: private Set<Reminder> localReminders = new HashSet<Reminder>();

When updating this set, I'm using localreminders.contains(anotherReminder) and for some reason that I've been trying to figure out for a while now, it does not call the overridden equals method. Even though cronExpression, subject, timeout and type of the reminders compared are the same, contains returns false.
So far I've only come across answers where equalsand/or hashcode were implemented incorrectly or not at all. Any help would be very much appreciated!

Let me know if you need more information like additional code for this!

EDIT: the properties used in hashcodeand equals are all String, except for timeout which is int.

EDIT2: while debugging, I currently have these two reminders in my HashSet:
Reminder [cronExpression=0 10 10 ? * *, subject=, type=OTHER_TYPE, audioPath=/other_type_reminder.mp3, muted=false, future=DelegatingErrorHandlingRunnable for Task@af94b0, timeout=35940]

Reminder [cronExpression=50 53 10 ? * *, subject=sub, type=TYPE, audioPath=/type_reminder.mp3, muted=false, future=DelegatingErrorHandlingRunnable for ReminderTask@f1f373, timeout=35940]

The one that I am checking whether it is contained in my set looks like this:
Reminder [cronExpression=50 53 10 ? * *, subject=sub, type=TYPE, audioPath=/type_reminder.mp3, muted=false, future=null, timeout=35940]

The only difference I can spot here is that in one, the future is null while it is actually set in the other. But since the future property is not included in either hashcode or ´equals`, this should not matter.

3 Answers3

3

As you can see in the implementation of the equals method you call cronExpression.equals(other.cronExpression) and subject.equals(other.subject) and type.equals(other.type). If only one of this is not implemented right then you get wrong result. Please check if all of the properties that you use in this method has correct implementation of equals.

By the way also check the implementation of the methods cronExpression.hashCode(), subject.hashCode() and type.hashCode(). They are used in your hashCode method.

Edit: If as you said cronExpression, subject and type are Strings then it should be easy for you to make main method populate two objects from class Reminder with the same info and test the methods. To be sure where is the problem you can call if(firstReminder.equals(secondReminder)).

From my experiance you can have problems with the strings. For example if one of the string has space at the end is different then the other or similar kind of issue.

Edit 2: Ok, from your input It seems this objects to have the same strings. Is it possible Reminder class to be extended and you to compare child class object with Reminder object? If this happen in the child class equals and hashcode can be implemented and then the result can be wrong.

Also just be sure can you log the size of each string? This is very strange. Maybe it is possible you to have hidden character. See this for more information: Is there an invisible character that is not regarded as whitespace?

Good luck!

Level_Up
  • 789
  • 5
  • 19
  • I've edited my post to show that the properties `cronExpression`, `subject` and `type` are `String`, `timeout`is an int. Their implementations of equals should be correct. – Johanna Egger Nov 21 '18 at 09:21
  • I have unit-tested the `equals` method of the `Reminder` and gotten the results I was looking for. I will do that for the `hashcode` method as well and see if I can unearth anything. But since it was generated using Eclipse, I didn't think there could be a problem. – Johanna Egger Nov 21 '18 at 09:28
  • If your tests pass successfully this will prove that in runtime your data is different. As I said your strings can look the same but to be different containing symbols that you can not see. – Level_Up Nov 21 '18 at 09:31
0

The Problem may be with your hashcode() method. It should generate a unique code. There are some guidelines to overridde hashcode().Hashcode Best Practice

If hashcode of objects are different then equals() will not called even if they are equal.
Because HashSet first check hashcodes of both objects and if hashcodes are equal then only it will call equals() to check whether both objects are really equal or not.

Read Oracle Javadoc to override hashcode override contract

Tarun
  • 986
  • 6
  • 19
  • the hashCode() method looks fine – Mick Nov 21 '18 at 08:51
  • @Mick It may look fine but if it generate different hashcode for same objects then equals will not called and objects will be considered as different...Dont know his hashcode is generating what hashcodes – Tarun Nov 21 '18 at 08:53
  • Indeed, *this* hashCode is implemented properly. It's the hashCode implementation of the contained objects that is in question here. – Mark Jeronimus Nov 21 '18 at 09:46
  • The properties used in both `hashcode` and `equals` are `String`s and one integer and therefore, `hashcode` implementations should be working correctly. – Johanna Egger Nov 21 '18 at 10:08
  • @Tarun: looks like I can't chat just yet since this is my very first question/interaction on stackoverflow. I'm currently doing some debugging and will edit my post accordingly with my findings. – Johanna Egger Nov 21 '18 at 11:06
  • @jegger okay, I suggest you to take your objects which you considered as same but HashSet is considering them different and generate thier hashcodes and compare them , They should be same and if they are different then there is some problem – Tarun Nov 21 '18 at 11:23
  • I just edited my initial post with the results I'm currently getting while debugging. It shows the two `Reminder` in the hashset and the one that is being compared. Based on that example, I would assume that it is contained in the set but the results I'm getting say otherwise. – Johanna Egger Nov 21 '18 at 11:25
  • @jegger Just print hashCodes of both Reminder Objects, 2 and 3 object in your question, print it in Hashcode method above `return result;` – Tarun Nov 21 '18 at 11:28
0

You need to provide us the import of the Reminder class if you want us to be able to help you.

For your culture and curiosity : java.util.HashSet.contains(Object o), reading the code it points to :

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

which itself points to :

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

As you can see, the important part of your implementation is Reminder.hashCode().


Regarding your specific issue : As you are probably using quartz for org.quartz.CronExpression, you can see that org.quartz.CronExpression.hashCode() method is not implemented, so it calls it's parent hashCode(), which is Object.hashCode(). From the documentation (JRE 7), you can read :

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the JavaTM programming language.)

So both of similar item with different instance of org.quartz.CronExpression will have different hashCode() result.

Vincent C.
  • 607
  • 5
  • 18
  • The properties cronExpression, subject and type are Strings, timeout is an int, so their hashcode implementations should be working correctly. – Johanna Egger Nov 21 '18 at 09:17