Java Hashset.contains() produces mysterious result

2019-02-08 12:25发布

I don't usually code in Java, but recently I started not having a choice. I might have some major misunderstanding of how to properly use HashSet. So it might be possible something I did is just plain wrong. However I'm grateful for any help, you might offer. So the actual problem:

In a small program I was writing, I was generating very similar objects, which, when created, would have a very specific id (a string or in my last iteration a long). Because each object would spawn new objects, I wanted to filter out all those I already created. So I started throwing the id of every new object into my Hash(Set) and testing with HashSet.contains(), if an object was created before. Here is the complete code:

// hashtest.java
import java.util.HashSet;

class L {
    public long l;
    public L(long l) {
        this.l = l;
    }
    public int hashCode() {
        return (int)this.l;
    }
    public boolean equals(L other) {
        return (int)this.l == (int)other.l;
    }
}

class hashtest {
    public static void main(String args[]) {
        HashSet<L> hash = new HashSet<L>();
        L a = new L(2);
        L b = new L(2);
        hash.add(a);
        System.out.println(hash.contains(a));
        System.out.println(hash.contains(b));
        System.out.println(a.equals(b));
        System.out.println(a.hashCode() == b.hashCode());
    }
}

produces following output:

true
false
true
true    

so apparently, contains does not use the equals function provided by L, or I have some major misunderstanding of the concept ...

I tested it with openjdk (current version included in ubuntu) and the official current java from Oracle on Win7

for completeness official java-api documentation for HashSet.contains():

public boolean contains(Object o)

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)).

http://download.oracle.com/javase/6/docs/api/java/util/HashSet.html#contains(java.lang.Object)

Any ideas or suggestions?

3条回答
三岁会撩人
2楼-- · 2019-02-08 13:11

When you are adding objects to a set it internally calls equals and hashCode methods. You have to override these two methods. For example I have taken one bean class with name,id,designation, then created and added an employee object.

HashSet<Employee> set = new HashSet<Employee>();
Employee employee = new Employee();
employee.setId(1);
employee.setName("jagadeesh");
employee.setDesignation("programmer");
set.add(employee);
Employee employee2 = new Employee();
employee2.setId(1);
employee2.setName("jagadeesh");
employee2.setDesignation("programmer");
set.add(employee2);

set.add() calls internally the equals and hashCode methods. So you have to override these two methods in your bean class.

@Override
public int hashCode(){
    StringBuffer buffer = new StringBuffer();
    buffer.append(this.name);
    buffer.append(this.id);
    buffer.append(this.designation);
    return buffer.toString().hashCode();
}
@Override
public boolean equals(Object object){
    if (object == null) return false;
    if (object == this) return true;
    if (this.getClass() != object.getClass())return false;
    Employee employee = (Employee)object;
    if(this.hashCode()== employee.hashCode())return true;
   return false;
}   

Here we are overriding equals() and hashCode(). When you add an object to the HashSet method it internally iterates all objects and calls the equals method. Hence we overrid hashCode, it compares every objects hashCode with its current hashCode and returns true if both are equal, else it returns false.

查看更多
我只想做你的唯一
3楼-- · 2019-02-08 13:12

Your equals method needs to take an Object.
Because you declared it as taking an L, it becomes an additional overload instead of overriding the method.
Therefore, when the hashSet class calls equals, it resolves to the base Object.equals method. When you call equals, you call your overload because a and b are both declared as L instead of Object.

To prevent this issue in the future, you should add @Override whenever you override a method.
This way, the compiler will warn you if it isn't actually an override.

查看更多
对你真心纯属浪费
4楼-- · 2019-02-08 13:19

You're not actually overriding Object.equals; instead, you're defining a new method with the same name but different parameters. Notice that Object.equals takes an Object argument, while your equals method takes an L argument. If you rewrite your equals method to take an Object and perform the necessary type-checking/casting to L at runtime, then your code is work as you expect.

Also, this is why you really should use @Override annotations whenever your JRE supports them. That way, the compiler will complain if you accidentally implement a new method when you intend to override an existing one.

By way of an example, this equals method should work correctly. (And, on an unrelated note, it won't fail if the object being compared to is null.)

@Override
public boolean equals(Object other) {
    return other != null && other instanceof L && this.l == ((L)other).l;
}
查看更多
登录 后发表回答