简体   繁体   中英

Maintaining hashCode contract for the specific condition, equals() depending on two integers

I have a basic class with the structure:

class Employee {
int eId;
String eName;

Employee(int id, String name) {
    this.eId= id;
    this.eName= name;
}

The conditions for equality is such that equals() should return true if any of the following is true:

  1. eId are same.
  2. eName are same.
  3. Lengths of eName are same.

I had no problem in overriding equals() , however, in order to maintain the hash code contract, I should override hashCode() as well. So, the hashCode should depend on eId and eName.length() (if eName s are equal, their lengths will be equal as well). So there are four cases:

Employee e1 = new Employee(4, "John");
Employee e2 = new Employee(3, "Jane");
Employee e3 = new Employee(4, "Jim");
Employee e4 = new Employee(7, "Random");

hashCode() should return the same value for e1 , e2 , and e3 and a different value for e4 . I can't come up with the logic satisfying this requirement.

Here is the exact problem:

Create a class (having parameters name, id etc). Show that if 2 objects of this class will be compared then they should return true in any of below case :

A. Id of both are same.

B. Name of both are same.

C. Length of name's of both are same.

Make sure HashCode contract should not violate.

You're running into trouble because your notion of equality is inconsistent. Specifically, it's not transitive, which the contract for .equals() requires.

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 .

Under your definition, e1 equals e2 and e3 , but e2 does not equal e3 . This is incompatible with Java's notion of equality. This is also why you're running into trouble defining a reasonable .hashCode() implementation.

However what you can do is define a custom Comparator (or Ordering , if you're using Guava). For most use cases (like sorting, searching, or filtering) you should be able to use a separate Comparator instance just like you would the .equals() method. You are effectively trying to define equivalent objects, not equal objects.

If you can't use a separate Comparator for whatever reason, your Employee object will be fundamentally inconsistent, and will prove problematic even if you should get a "workable" .hashCode() implemented.

I guess, you cannot write a consistent hashCode in your case, because your equals breaks the contract of Object.equals method, namely transitivity:

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 .

Suppose you have this code:

Employee a = new Employee(1, "John");
Employee b = new Employee(1, "James");
Employee c = new Employee(2, "James");

In this case with your equals operation a.equals(b) and b.equals(c) , but not a.equals(c) .

I'd suggest to rethink the implementation of equals . Probably your Employee has either eId or eName defined, so it might be better to add a boolean field which says whether eId or eName should be used. This way you can easily implement equals and hashCode . This way the implementation might be like this (assuming for simplicity that eName cannot be null ):

class Employee {
    boolean useName;
    int eId = 0;
    String eName;

    Employee(int id) {
        this.eId = id;
        this.useName = false;
    }

    Employee(String name) {
        this.eName = name;
        this.useName = true;
    }

    @Override
    public int hashCode() {
        return useName ? eName.length() * 1337 : eId * 7331;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        if (useName != other.useName)
            return false;
        if (useName) {
            if (eName.length() != other.eName.length())
                return false;
        } else {
            if (eId != other.eId)
                return false;
        }
        return true;
    }
}

If you use Java 7, then I think it's easy by using java.util.Objects:

@Override
public int hashCode() {
    return Objects.hash(eld,eName.length());
}

Or you can can consider Guava , it has a same method under com.google.common.base.Objects

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM