ArrayList не использует переопределенные равно - PullRequest
10 голосов
/ 06 июля 2011

У меня проблема с получением ArrayList для правильного использования переопределенных равных. проблема в том, что я пытаюсь использовать равенства для проверки только для одного ключевого поля, и с помощью 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")); 
    }    
}

Что меня беспокоит, так это то, что я не только получаю ложные данные на выходе, но я также не получаю вывод "достигнуто здесь".

У кого-нибудь есть идеи, почему это переопределение полностью игнорируется? Есть ли какая-то тонкость с переопределениями и внутренними классами, о которых я не знаю?

Edit: Возникли проблемы с сайтом, поэтому я не могу отметить ответ. Спасибо за быстрый ответ: да, упущение с моей стороны, что называется String .equals, а не мой пользовательский. Полагаю, сейчас это старомодные чеки

Ответы [ 11 ]

16 голосов
/ 06 июля 2011

Если вы проверите источники 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.

7 голосов
/ 06 июля 2011

Согласно 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.

К сожалению, это означает, что ваш вариант использования (aпользовательский класс, который может быть равен другому стандартному классу) не может быть реализован полностью соответствующим образом.

Если вы все еще хотите сделать что-то вродедля этого вам придется внимательно прочитать спецификацию (а иногда и реализацию) всех ваших классов коллекций и проверить наличие таких ловушек, как этот.

3 голосов
/ 06 июля 2011

Вы вызываете 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.

2 голосов
/ 06 июля 2011

Как правило, вам также необходимо переопределить 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;
} 

Вы можете сделать то же самое с помощью метода 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);
   }
}
1 голос
/ 06 июля 2011

Ваша равная реализация неверна. Ваш параметр не должен быть 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, поэтому вам не нужно сначала проверять наличие нуля).

Затем вы бы проверили наличие эквивалентного объекта в вашем списке, используя:

objectList.contains(new InnerClass("UNIQUE ID1"));

Но если вы действительно хотите проверить InnerClass по ключу String, почему бы не использовать Map<String,InnerClass> вместо этого?

0 голосов
/ 02 июня 2017

Этот пост был впервые написан до того, как была доступна Java 8, но теперь, когда наступил 2017 год, вместо использования метода List.containts (...) вы можете использовать новый способ Java 8, например:

System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent());

И дайте вашему TestClass получатель для вашего поля testKey:

public String getTestKey() {

   return testKey;
}

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

0 голосов
/ 16 сентября 2016

В вашем коде есть две ошибки.

Первое: метод «содержащий», вызываемый для объекта «objectList», должен передать в качестве параметра новый объект InnerClass.

Второе: равноМетод (должен принимать параметр как 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;
        }       
    }  
0 голосов
/ 22 мая 2014

Как говорилось во многих постах, проблема в том, что функция list.indexOf (obj) вызывает "равно" для obj, а не для элементов в списке.

У меня была такая же проблема и "содержит () "не удовлетворил меня, так как мне нужно знать, где находится элемент !.Мой подход - создать пустой элемент с параметром для сравнения, а затем вызвать indexOf.

Реализовать такую ​​функцию, как

public static InnerClass empty(String testKey) {
    InnerClass in = new InnerClass();
    in.testKey =testKey;
    return in;
}

И затем вызвать indexOf следующим образом:

ind position = list.indexOf(InnerClass.empty(key));
0 голосов
/ 23 мая 2013

В противном случае ваш метод равенства вызывается, если вы измените код следующим образом.надеюсь, что это проясняет концепцию.

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)); 
    }    
}
0 голосов
/ 06 июля 2011

Есть несколько проблем с вашим кодом. Мое предложение состояло бы в том, чтобы избежать полного переопределения равенства, если вы не знакомы с ним , и расширить его в новую реализацию, например ...

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");

Я предлагаю это, потому что, если вы переопределяете equals, вы должны переопределить hashCode, и, похоже, вам не хватает некоторых знаний в этой области - так что я бы просто избегал этого.

Кроме того, метод contains вызывает метод equals, поэтому вы видите «достигнуто здесь». Опять же, если вы не понимаете поток вызовов, я бы просто избегал его.

...