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