Коллекция Java Set - переопределить метод equals

Есть ли способ переопределить метод equals, используемый типом данных Set? Я написал собственный метод equals для класса с именем Fee. Теперь у меня есть LnkedList из Fee, и я хочу убедиться, что нет повторяющихся записей. Таким образом, я рассматриваю возможность использования Set вместо LinkedList, но критерии для принятия решения о том, равны ли две комиссии, находятся в переопределенном методе equals в классе Fee.

При использовании LinkedList мне придется перебирать каждый элемент списка и вызывать переопределенный метод equals в классе Fee с оставшимися записями в качестве параметра. Простое чтение этого само по себе звучит как слишком большая обработка и добавит вычислительной сложности.

Могу ли я использовать Set с переопределенным методом equals? Нужно ли мне?


person Faiyet    schedule 31.05.2011    source источник


Ответы (5)


Как сказал Джефф Фостер:

Метод Set.equals() используется только для сравнения двух наборов на предмет равенства.

Вы можете использовать Set, чтобы избавиться от повторяющихся записей, но будьте осторожны: HashSet не использует методы equals() содержащих его объектов для определения равенства.

HashSet содержит внутренний HashMap с <Integer(HashCode), Object> записями и использует equals(), а также метод equals HashCode для определения равенства.

Один из способов решить эту проблему — переопределить hashCode() в классе, который вы поместили в набор, чтобы он представлял ваши equals() критерии.

Например:

class Fee {
      String name;

  public boolean equals(Object o) {
      return (o instanceof Fee) && ((Fee)o.getName()).equals(this.getName());
  }

  public int hashCode() {
      return name.hashCode();
  }

}
person Jonas Eicher    schedule 20.07.2012
comment
HashSet следует контракту Set, который требует использования метода equals() для определения равенства. Но он использует тот факт, что Object должен иметь тот же hashCode(), если он equals() другой объект. Вы можете иметь несколько объектов с одинаковым значением hashCode() в одном HashSet. - person Martin; 15.11.2013
comment
Хороший вопрос, Мартин. Я только что проверил это в небольшом тестовом приложении. Если вы добавляете объект, который не равен NOR и не имеет того же хэш-кода, что и набор объектов, он добавляется как новый. Я уточнил это в своем ответе. - person Jonas Eicher; 02.12.2013

Вы можете и должны использовать Set для хранения типа объекта с переопределенным методом equals, но вам также может понадобиться переопределить hashCode(). Равные объекты должны иметь одинаковые хеш-коды.

Например:

public Fee{

    public String fi;

    public String fo;

    public int hashCode(){

        return fi.hashCode() ^ fo.hashCode();
    }

    public boolean equals(Object obj){

        return fi.equals(obj.fi) && fo.equals(obj.fo);
    }
}

(Конечно, при необходимости с нулевыми проверками.)

Наборы часто используют hashCode() для оптимизации производительности и будут работать неправильно, если ваш метод hashCode не работает. Например, HashSet использует внутренний HashMap.

Если вы проверите исходный код HashMap, вы увидите, что он зависит как от методов hashCode(), так и от методов equals() элементов для определить равенство:

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

Если хеш сгенерирован неправильно, ваш метод equals может никогда не вызваться.

Чтобы сделать ваш набор быстрее, вы должны генерировать разные хэш-коды для объектов, которые не равны, где это возможно.

person SharkAlley    schedule 12.06.2013
comment
Я знаю, что это пример, но вы не должны использовать конкатенацию для генерации хэш-кода. Как вы сказали, метод hashCode может вызываться часто, а конкатенация строк — медленная и дорогая операция. Лучшим способом сделать это было бы просто XOR строки hashCode. Например: return fi.hashCode() ^ fo.hashCode(); Кроме того, ваш equals()метод немного избыточен. Вам не нужно сравнивать fi с fo, а затем сравнивать fo с fi. В Object Javadoc ясно указано, что equals() метод должен быть симметричным. Поэтому достаточно выполнить только fi.equals(fo) (игнорировать null). - person Moinonime; 17.07.2013
comment
Спасибо Матье. Я отредактировал свой ответ на XOR хэш-кодов, а не на объединение строк. Я не думаю, что ваш комментарий относительно метода equals действителен. fi.equals(fo) будет совершенно другим сравнением и не будет соответствовать методу hashCode, который я определил. - person SharkAlley; 20.07.2013

Set использует метод equals объекта, добавленного в набор. В JavaDoc указано

Коллекция, не содержащая повторяющихся элементов. Более формально, наборы не содержат пары элементов e1 и e2, таких что e1.equals(e2), и не более одного нулевого элемента.

Метод Set.equals() используется только для сравнения двух наборов на предмет равенства. Он никогда не используется как часть добавления/удаления элементов из набора.

person Jeff Foster    schedule 31.05.2011

Одним из решений может быть использование TreeSet. с компаратором.

Из документации:

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

Этот подход будет намного быстрее, чем использование LinkedList, но немного медленнее, чем HashSet (ln(n) против n).

Стоит отметить, что одним из побочных эффектов использования TreeSet будет то, что ваш набор будет отсортирован.

person Paul Wintz    schedule 06.09.2017

В Apache Commons Collection есть PredicatedList или PredicatedSet.

person Grooveek    schedule 31.05.2011
comment
Не могли бы вы уточнить, как это отвечает ОП? Я ищу способ игнорировать верхний/нижний регистр для Set<String>.contains("foo") - person Michael Scheper; 16.07.2014