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.
Как я могу сделать эту работу?
Полный код
Ответы
Ответ 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.