ryvantage
ryvantage

Reputation: 13486

Weird Set.contains() behavior

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

Answers (1)

Craig
Craig

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

Related Questions