List.contains() fails while .equals() works

idlackage picture idlackage · Feb 16, 2016 · Viewed 12k times · Source

I have an ArrayList of Test objects, which use a string as the equivalency check. I want to be able to use List.contains() to check whether or not the list contains an object that uses a certain string.

Simply:

Test a = new Test("a");
a.equals("a"); // True

List<Test> test = new ArrayList<Test>();
test.add(a);
test.contains("a"); // False!

Equals and Hash function:

@Override
public boolean equals(Object o) {
    if (o == null) return false;
    if (o == this) return true;
    if (!(o instanceof Test)) {
        return (o instanceof String) && (name.equals(o));
    }
    Test t = (Test)o;
    return name.equals(t.GetName());
}

@Override
public int hashCode() {
    return name.hashCode();
}

I read that to make sure contains works for a custom class, it needs to override equals. Thus it's super strange to me that while equals returns true, contains returns false.

How can I make this work?

Full code

Answer

Eran picture Eran · Feb 16, 2016

Just because your Test's equals may return true when you pass a String to it doesn't mean that String's equals will ever return true when you pass a Test instance to it. In fact, String's equals can only return true when the instance passed to it is another String :

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) { // the passed instance must be a String
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

ArrayList's contains calls indexOf which uses the equals method of the searched instance (the String "a" in your example), not the element type of the List (which is Test in your case) :

public int indexOf(Object o) {
    if (o == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i])) // o in your case is a String while
                                          // elementData[i] is a Test
                                          // so String's equals returns false
                return i;
    }
    return -1;
}