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;
}

Преимущество такого подхода заключается в том, что вам не нужно изменять метод равенства или хэша, и вы будете выглядеть как босс своим сверстникам!