Reputation: 13486
I initially started this as a test for a theory-based, best-practices question that I wanted to ask here, but in the process I found some interesting behavior in the java.Set class. Initially, I wanted to know any potential pitfalls of this approach, but now that I can see it doesn't work at all, I'd like to know why.
I have some objects that are containers for database objects for my app. The objects all have unique integer id
's, and hashCode()
and equals()
are defined by the integer ids (for storage in hashsets).
Well, I wanted the ability to check if a hashset contains the object given only the id. Certainly, I could create a new instance of the object and check that way. But, just for kicks, I wanted to see if I could accomplish it. Of course, this is also trivial with a hashmap, so this is really not an important question, just for fun and knowledge.
So, I made a class, and tried to call contains()
on an integer
, instead of an instance of the object. Netbeans, of course, gives a fun warning for this
Suspicious call to java.util.Collection.contains:
Given object cannot contain instances of int (expected Person)
Ignoring the error and running the code, I was shocked to find that Java does not even call the equals method. I placed debugging System.out.println()
s in my equals method to verify, and yep, it's not even being called.
In the code posted below, the expected output should be (if my theory was correct):
Here Yes Here Yes
or (if my theory was incorrect):
Here Yes Here No
However, the output is:
Here Yes No
Notice, there's no "Here" before the "No" proving that the equals method is not even being called.
Can anyone shed light? I was always told to add this to equals()
for efficiency:
if (!(obj instanceof Person))
return false;
But if equals()
is not even called in such a situation, then that would be pointless.
Here is the SSCCE:
Thanks for your time.
import java.util.LinkedHashSet;
import java.util.Set;
/**
*
* @author Ryan
*/
public class Test7 {
public static void main(String[] args) {
class Person {
public final int id;
public final String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object obj) {
System.out.println("Here");
if (this == obj)
return true;
if (obj instanceof Person)
return id == ((Person)obj).id;
else if(obj instanceof Integer)
return id == (Integer)obj;
else {
System.out.println("Returning False");
return false;
}
}
@Override
public int hashCode() {
return id;
}
}
Set<Person> set = new LinkedHashSet<Person>();
set.add(new Person(1, "Bob"));
set.add(new Person(2, "George"));
set.add(new Person(3, "Sam"));
if(set.contains(new Person(1, "Bob")))
System.out.println("Yes");
else
System.out.println("No");
if(set.contains(1))
System.out.println("Yes");
else
System.out.println("No");
}
}
Upvotes: 4
Views: 1033
Reputation: 1390
This is due to that fact that the comparison is done on the provided object not the elements in the set. From HashSet#contains(Object):
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)).
So in your example, you would be doing comparison like integer.equals(person)
. So if your set contains Person
objects, the if(obj instanceof Integer)
condition will never be checked, but if your set contained Integer
objects, that condition would be satisfied and as such would be checked.
Upvotes: 5