ArrayList не использует переопределенные равные
У меня возникла проблема с тем, чтобы ArrayList правильно использовал значение overriden. проблема в том, что я пытаюсь использовать равные, чтобы тестировать только одно ключевое поле и использовать ArrayList.contains() для проверки существования объекта с правильным полем. Вот пример
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
@Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof String) {
String inString = (String) in;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
//add some entries
objectList.add(new InnerClass("UNIQUE ID1", 42));
System.out.println( objectList.contains("UNIQUE ID1"));
}
}
Что меня беспокоит, так это то, что я не только ошибаюсь на выходе, но и не получаю выход "достигнутый здесь".
Есть ли у кого-нибудь идеи, почему это переопределение полностью игнорируется? Есть ли какая-то тонкость с переопределениями и внутренними классами, о которых я не знаю?
Изменить:
Возникли проблемы с сайтом, поэтому я не могу отметить ответ.
Спасибо за быстрый ответ: да, с моей стороны, надзор, что это String.equals thta называется, а не мой пользовательский. Я предполагаю, что это были старомодные проверки на данный момент
Ответы
Ответ 1
Если вы проверяете источники ArrayList
, вы увидите, что он вызывает equals
другого объекта. В вашем случае он вызовет equals
из String "UNIQUE ID1"
, который будет проверять, что другой объект не имеет тип String
и просто возвращает false
:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
...
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
...
return -1;
}
Для вашего случая вызов contains
с InnerClass
, который содержит только id
:
objectList.contains(new InnerClass("UNIQUE ID1"))
Не забудьте реализовать equals
для InnerClass
, который сравнивает только id
.
Ответ 2
В соответствии с JavaDoc List.contains(o)
определено, что он возвращает true
тогда и только тогда, когда этот список содержит хотя бы один элемент e
такой, что (o==null ? e==null : o.equals(e))
.
Обратите внимание, что это определение вызывает equals
на o
, который является параметром и не элементом, который находится в List
.
Поэтому String.equals()
будет вызываться, а не InnerClass.equals()
.
Также обратите внимание, что контракт для Object.equals()
гласит, что
Он симметричен: для любых ненулевых опорных значений x
и y
, x.equals(y)
должен возвращать true
тогда и только тогда, когда y.equals(x)
возвращает true
.
Но вы нарушаете это ограничение, так как new TestClass("foo", 1).equals("foo")
возвращает true
, но "foo".equals(new TestClass("foo", 1))
всегда возвращает false
.
К сожалению, это означает, что ваш прецедент (пользовательский класс, который может быть равен другому стандартному классу) не может быть полностью реализован.
Если вы все еще хотите сделать что-то подобное, вам придется внимательно прочитать спецификацию (а иногда и реализацию) всех ваших классов коллекции очень и проверить наличие ошибок, таких как это.
Ответ 3
Вы вызываете contains
аргументом, что String
, а не InnerClass
:
System.out.println( objectList.contains("UNIQUE ID1"))
В моем JDK:
public class ArrayList {
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
// omitted for brevity - aix
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<<
return i;
}
return -1;
}
}
Обратите внимание, как indexOf
вызывает o.equals()
. В вашем случае o
является String
, поэтому ваш objectList.contains
будет использовать String.equals
, а не InnerClass.equals
.
Ответ 4
Как правило, вам нужно также переопределить hashCode()
, но это не главная проблема. У вас есть асимметричный метод equals(..)
. Документы дают понять, что они должны быть симметричными:
Он симметричен: для любых непустых опорных значений x и y x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true.
И то, что вы наблюдаете, является неожиданным поведением из-за нарушенного контракта.
Создайте метод утилиты, который выполняет итерацию всех элементов и проверяет с помощью equals(..)
в строке:
public static boolean containsString(List<InnerClass> items, String str) {
for (InnerClass item : items) {
if (item.getTestKey().equals(str)) {
return true;
}
}
return false;
}
Вы можете сделать аналогичную вещь с помощью метода guava Iterables.any(..)
:
final String str = "Foo";
boolean contains = Iterables.any(items, new Predicate<InnerClass>() {
@Override
public boolean apply(InnerClass input){
return input.getTestKey().equals(str);
}
}
Ответ 5
Ваша реализация equals неверна. Ваш параметр не должен быть String
. Он должен быть InnerClass
.
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof InnerClass) return false;
InnerClass that = (InnerClass)o;
// check for null keys if you need to
return this.testKey.equals(that.testKey);
}
(Обратите внимание, что instanceof null
возвращает false, поэтому вам не нужно сначала проверять значение null).
Затем вы проверили бы существование эквивалентного объекта в своем списке, используя:
objectList.contains(new InnerClass("UNIQUE ID1"));
Но если вы действительно хотите проверить ключ InnerClass на String, почему бы не использовать Map<String,InnerClass>
вместо этого?
Ответ 6
Хотя ваш ответ не отвечает на ваш вопрос, во многих Коллекциях используется hashcode()
. Вы должны переопределить это, чтобы "согласиться" с equals()
.
Собственно, вы всегда должны реализовывать вместе equals
и hashcode
вместе, и они всегда должны быть согласованы друг с другом. Поскольку javadoc для Object.equals()
указывает:
Обратите внимание, что обычно необходимо переопределять метод hashCode всякий раз этот метод переопределяется, чтобы поддерживать общий hashCode, в котором говорится, что равные объекты должны иметь равный хэш коды.
В частности, многие Коллекции полагаются на соблюдение этого контракта - поведение undefined в противном случае.
Ответ 7
В коде есть несколько проблем. Мое предложение состояло в том, чтобы избежать переопределения равных полностью, если вы не знакомы с ним, и распространите его на новую реализацию, например...
class MyCustomArrayList extends ArrayList<InnerClass>{
public boolean containsString(String value){
for(InnerClass item : this){
if (item.getString().equals(value){
return true;
}
}
return false;
}
}
Затем вы можете сделать что-то вроде
List myList = new MyCustomArrayList()
myList.containsString("some string");
Я предлагаю это, потому что если вы переопределяете равные, то также следует переопределить hashCode, и кажется, что вам не хватает знаний в этой области, поэтому я бы просто избегал этого.
Кроме того, метод contains вызывает метод equals, поэтому вы видите "достигнутый здесь". Опять же, если вы не понимаете поток вызовов, я бы просто избегал этого.
Ответ 8
иначе вы получите свой равный метод, если вы измените свой код следующим образом. надеюсь, это очистит концепцию.
package com.test;
import java.util.ArrayList;
import java.util.List;
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
@Override
public boolean equals (Object in1) {
System.out.println("reached here");
if(in1 == null) {
return false;
}else if( in1 instanceof InnerClass) {
return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
InnerClass in1 = new InnerClass("UNIQUE ID1", 42);
InnerClass in2 = new InnerClass("UNIQUE ID1", 42);
//add some entries
objectList.add(in1);
System.out.println( objectList.contains(in2));
}
}
Ответ 9
Как отмечалось многими сообщениями, проблема в том, что функция list.indexOf(obj) вызывает "равно" obj, а не элементы в списке.
У меня была та же проблема, и "contains()" меня не удовлетворил, так как мне нужно знать, где элемент!. Мой aproach состоит в том, чтобы создать пустой элемент с параметром для сравнения, а затем вызвать indexOf.
Внедрить такую функцию,
public static InnerClass empty(String testKey) {
InnerClass in = new InnerClass();
in.testKey =testKey;
return in;
}
А затем вызовите indexOf следующим образом:
ind position = list.indexOf(InnerClass.empty(key));
Ответ 10
В коде есть две ошибки.
Во-первых:
Метод "contains", называемый объектом objectList, должен передать новый объект InnerClass в качестве параметра.
Во-вторых:
Метод equals (должен принимать параметр как Object и корректен) должен правильно обрабатывать код в соответствии с принятым объектом.
Вот так:
@Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof InnerClass) {
String inString = ((InnerClass)in).testKey;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
Ответ 11
Этот пост был сначала написан до того, как Java 8 был доступен, но теперь, когда он 2017 вместо использования метода List.containts(...), вы можете использовать новый способ Java 8 следующим образом:
System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent());
И дайте вашему TestClass getter для вашего поля testKey:
public String getTestKey() {
return testKey;
}
Преимущество такого подхода заключается в том, что вам не нужно изменять метод равенства или хэша, и вы будете выглядеть как босс своим сверстникам!