Подтвердить что ты не робот

List.contains() не работает, а .equals() работает

У меня есть ArrayList объектов Test, которые используют строку в качестве проверки эквивалентности. Я хочу иметь возможность использовать List.contains(), чтобы проверить, содержит ли список объект, который использует определенную строку.

Просто:

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

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

Равные и Хэш-функции:

@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();
}

Я прочитал, что для того, чтобы contains работал для настраиваемого класса, он должен переопределить equals. Таким образом, мне очень странно, что в то время как equals возвращает true, contains возвращает false.

Как я могу сделать эту работу?

Полный код

4b9b3361

Ответ 1

Просто потому, что ваш Test equals может возвращать true, когда вы передаете ему String, это не означает, что String equals когда-либо вернет true, когда вы передадите ему экземпляр Test. Фактически, String equals может возвращать только true, когда экземпляр, переданный ему, является другим 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 contains вызывает indexOf, который использует метод equals искомого экземпляра (String "a" в вашем примере), а не тип элемента List (который является Test в вашем случае):

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 equals returns false
                return i;
    }
    return -1;
}

Ответ 2

equals() всегда должен быть commutative, т.е. a.equals(b) и b.equals(a) всегда должны возвращать одно и то же значение. Или симметрично, поскольку javadoc equals() вызывает его:

Метод equals реализует отношение эквивалентности для ненулевых ссылок на объекты:

  • Это рефлексивно: для любого ненулевого опорного значения x, x.equals(x) должен возвращать true.
  • Он симметричен: для любых ненулевых опорных значений x и y x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true.
  • Он является транзитивным: для любых ненулевых опорных значений x, y и z, если x.equals(y) возвращает true и y.equals(z) возвращает true, тогда x.equals(z) должен вернуться true.
  • Это согласовано: для любых ненулевых опорных значений x и y несколько вызовов x.equals(y) последовательно возвращают true или последовательно возвращают false, если не используются никакие данные, используемые в equals сравнениях на объекты изменены.
  • Для любого ненулевого опорного значения x, x.equals(null) должен возвращать false.

К сожалению, даже в Java Runtime Library это неправильно. Date.equals(Timestamp) будет сравнивать значения миллисекунд, игнорируя наносекунды, присутствующие в Timestamp, а Timestamp.equals(Date) возвращает false.

Ответ 3

Проблема заключается в том, что List<E>.contains(object o) документировано для возврата true:

тогда и только тогда, когда этот список содержит хотя бы один элемент e такой, что (o == null? e == null: o.equals(e)).

(Из https://docs.oracle.com/javase/8/docs/api/java/util/List.html#contains-java.lang.Object-)

Обратите внимание, что он не выполняет проверку как e.equals(o), что было бы необходимо для вашего теста. Ваш метод equals не работает коммутативно ( "симметрично", используя термины из документов Java).

Документы Java, что метод equals() для класса должен следовать этим правилам:

Метод equals реализует отношение эквивалентности на непустых ссылки на объекты:

  • Это рефлексивно: для любого ненулевого опорного значения x, x.equals(x) должна возвращать истинное.
  • Он симметричен: для любых ненулевых опорных значений x и y, x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true.
  • Это транзитивно: для любых ненулевых опорных значений x, y и z, если x.equals(y) возвращает true и y.equals(z) возвращает true, тогда x.equals(z) должен возвращать true.
  • Это согласуется: для любых ненулевых опорных значений x и y несколько вызовов x.equals(y) последовательно возвращают true или последовательно возвращают false, если информация, используемая при равных сравнениях с объектами, не изменяется.
  • Для любого ненулевого опорного значения x, x.equals(null) должна возвращать ложь.

Ответ 4

Если вы пишете

test.contains(new Test("a")); 

то он обязательно вернет true. Вы проверяете строковый объект в списке Test.