Коллекция наборов Java — переопределить метод equals

#java #set #overriding #equals

#java #установить #переопределение #равно

Вопрос:

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

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

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

Ответ №1:

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

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

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

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

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

Например:

 class Fee {
      String name;

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

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

}
  

Комментарии:

1. HashSet Следует Set контракту, который требует использовать equals() метод для определения равенства. Но в нем используется тот факт, что Object требуется, чтобы an имел то же самое, hashCode() если это equals() другой объект. У вас может быть несколько объектов с одинаковым hashCode() значением в одном HashSet .

2. Хорошая мысль, Мартин. Я только что проверил это в небольшом тестовом приложении. Если вы добавляете объект, который не равен и не имеет того же хэш-кода, что и set-objects, он добавляется как новый. Я разъяснил это в своем ответе.

Ответ №2:

Вы можете и должны использовать 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) amp;amp; fo.equals(obj.fo);
    }
}
  

(При необходимости, конечно, с проверками null).

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

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

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

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

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

Комментарии:

1. Я знаю, что это пример, но вы не должны использовать конкатенацию для генерации хэш-кода. Как вы сказали, метод hashCode может вызываться часто, а конкатенация строк является медленной и дорогостоящей операцией. Лучшим способом сделать это было бы просто XOR для строки hashCode. Например: return fi.hashCode() ^ fo.hashCode(); Кроме того, ваш equals() метод немного перегружен. Вам не нужно сравнивать fi с fo , а затем fo с fi . Object Javadoc проясняет, что equals() метод должен быть симметричным. Следовательно, fi.equals(fo) достаточно только performin (игнорировать null ).

2. Спасибо Матье. Я отредактировал свой ответ, чтобы использовать XOR для хэш-кодов, а не для объединения строк. Я не думаю, что ваш комментарий относительно метода equals действителен, хотя. fi.equals(fo) был бы совсем другим сравнением и не соответствовал бы методу hashCode, который я определил.

Ответ №3:

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

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

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

Ответ №4:

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

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

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

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

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

Ответ №5:

Комментарии:

1. Не могли бы вы подробнее рассказать о том, как это отвечает на OP? Я ищу способ игнорировать верхний / нижний регистр для Set<String>.contains("foo")