Методы HashCode () и Equals () можно сказать, что являются основной особенностью полностью объектно-ориентированной Java. Это облегчает наше программирование, а также приносит много опасностей. В этой статье мы обсудим, как правильно понять и использовать эти два метода.
Если вы решите переписать метод equals (), то вы должны четко прояснить риски, достигнутые при этом, и убедиться, что вы можете написать надежный метод Equals (). Одна вещь, которую вы должны отметить, это то, что после переписывания Equals () вы должны переписать метод HashCode (). Конкретные причины будут объяснены позже.
Давайте сначала посмотрим на описание метода equals () в спецификации Javase 7:
・ Это рефлексивное: для любого не ненулевого эталонного значения x, x.equals(x) должно вернуть true .
・ Это симметрично: для любых не нулевых эталонных значений x и y, x.equals(y) должны возвращать true если и только тогда, когда y.equals(x) возвращает true .
・ Это транзитивно: для любых не нулевых эталонных значений x, y, и z, если x.equals(y) возвращает true, а y.equals(z) возвращает true , то x.equals(z) должен вернуть true.
・ Это согласован: для любых не нулевых эталонных значений x и y , Многочисленные вызовы x.equals(y) последовательно возвращают true или последовательно возвращают false , не при условии, что не используется в сравнении с равными для объектов.
・ Для любого не ненулевого эталонного значения x, x.equals(null) должно возвращать false .
Этот отрывок использует много нумерологии в дискретной математике. Позвольте мне дать краткое объяснение:
1. Рефлексивность: A.Equals (a) должен вернуть True.
2. Симметрия: если a.equals (b) возвращает True, то B.Equals (a) также необходимо вернуть True.
3. Передача: если A.Equals (b) верно, а B.Equals (c) верно, то A.Equals (c) также должны быть истинными. По словам прямо, a = b, b = c, затем a = c.
4. Последовательность: до тех пор, пока состояние объектов A и B не изменяется, A.Equals (b) всегда должно возвращать True.
5. A.Equals (null), чтобы вернуть ложь.
Я считаю, что до тех пор, пока люди, которые не являются профессиональными в математике, не будут называть вышеперечисленные вещи. В фактическом приложении нам нужно только переписать метод equals () только в соответствии с определенными шагами. Для удобства объяснения мы сначала определили класс программиста (кодировщик):
Class Coder {private String name; частный int возраст; // Getters и Setters}Мы хотим, чтобы, если имена и возраст двух объектов программиста одинаковы, то мы думаем, что эти два программиста одинаковы. В настоящее время мы должны переписать его метод equals (). Поскольку по умолчанию Equalls () фактически определяет, указывают ли две ссылки на один и тот же объект, он эквивалентен ==. При переписывании, следуйте следующим трем шагам:
1. Определите, равно ли это себе.
if (Другое == это) вернуть true;
2. Используйте оператор экземпляра, чтобы определить, является ли другой объект кодера типа.
if (! (Другой экземпляр кодировщика)) вернуть false;
3. Сравните домены данных, имя и возраст, которые вы настраиваете в классе кодера, и вы не должны пропустить его.
Кодер O = (кодер) Другое; return o.name.equals (name) && o.age == age;
Видя это, кто -то может спросить, на шаге 3 есть актерский состав. Если кто -то передает объект целочисленного класса в это равные, он бросит ClasscastException? Это беспокойство на самом деле избыточно. Поскольку мы выступили с суждением о экземпляре на втором этапе, если другой является некодерным объектом, или даже другие являются нулевыми, то FALSE будет возвращен непосредственно на этом этапе, так что последующий код не будет получить возможность выполнить.
Вышеуказанные три шага также являются шагами, рекомендованными в <Эффективная Java>, которые в основном могут гарантировать, что нет ошибки.
В спецификации Javase 7,
«Обратите внимание, что обычно необходимо переопределить метод хэшкода всякий раз, когда этот метод (равен) переопределен, чтобы поддерживать общий контракт для метода хэшкода, который утверждает, что равные объекты должны иметь равные хэш -коды».
Если вы переписываете метод Equals (), не забудьте переписать метод HashCode (). Мы изучили хэш -таблицы на курсах по структуре компьютерных данных университета. Метод HashCode () служит хэш -таблице.
Когда мы используем класс сбора, который начинается с хэш -хэша, такого как Hashmap и Hashset, HashCode () будет вызвана неявно для создания хэш -картирования. Мы объясним это позже. Здесь мы сначала сосредоточимся на написании метода HashCode ().
<Эффективный Java> предоставляет метод письма, который может в большинстве случаев избежать конфликтов хэш, но я лично считаю, что нет необходимости делать столько проблем для общих приложений. Если вам нужно хранить десятки тысяч или миллионов объектов в вашем приложении, вы должны строго следовать методам, приведенным в книге. Если вы пишете небольшую и среднюю приложение, то достаточно следующих принципов:
Необходимо убедиться, что все члены объекта кодера могут быть отражены в хашкоде.
Для этого примера мы можем написать это:
@Override public int hashcode () {int result = 17; Результат = Результат * 31 + name.hashcode (); результат = результат * 31 + возраст; результат возврата; }Где int result = 17, вы также можете изменить его на 20, 50 и т. Д. Видя это, мне внезапно было любопытно и хотел посмотреть, как реализован метод HashCode () в классе строки. Проверьте документацию и знаю:
"Возвращает хэш -код для этой строки. Хэш -код для строкового объекта вычисляется как
s [0]*31^(n-1) + s [1]*31^(n-2) + ... + s [n-1]
Использование int arithmetic, где S [i] является ITH символом строки, n - длина строки, и ^ указывает на эксплуатацию. (Значение хеша пустой строки равно нулю.) "
Рассчитайте код ASCII каждого символа в мощность N - 1, а затем добавьте его. Можно видеть, что солнце очень строгое в реализации хэшкода. Это может избежать того же хэшкода в двух разных строках в наибольшей степени.
На концепцию ведра ссылается в реализации хэш -таблицы Oracle. Как показано на рисунке ниже:
Как видно из приведенного выше рисунка, хэш -таблица с ведром примерно эквивалентна комбинации хэш -таблицы и связанного списка. То есть связанный список будет повесенен на каждом ведре, и каждый узел связанного списка будет использоваться для хранения объектов. Java использует метод hashcode (), чтобы определить, какое ведро следует найти, а затем ищет его в соответствующем связанном списке. В идеале, если ваш метод hashcode () записан достаточно надежным, то каждое ведро будет иметь только один узел, который достигнет сложности времени поиска постоянного уровня. То есть, независимо от того, в какую часть памяти находится ваш объект, я могу немедленно найти область через Hashcode (), не проходя и не искавая от начала до конца. Это также главное применение хэш -таблиц.
нравиться:
Когда мы называем метод HASHSET POT (Object O), мы сначала найдем его в соответствующем ведре в соответствии с возвратным значением O.HashCode (). Если в ведре нет узлов, то положите здесь. Если уже есть узлы, то заведите O до конца связанного списка. Аналогичным образом, при вызове содержится (объект O), Java найдет соответствующее ведро через возвращаемое значение hashcode (), а затем вызовет метод equals () в свою очередь на узлах в соответствующем связанном списке, чтобы определить, является ли объект в узле, который вы хотите.
Давайте воспользуемся примером, чтобы испытать этот процесс:
Давайте сначала создадим два новых объекта программирования:
Кодимер C1 = новый кодировщик ("Bruce", 10); Кодимер C2 = новый кодировщик («Брюс», 10);Предположим, что мы переписываем метод кодера Equals () без перезаписывания метода HashCode ():
@Override public boolean equals (объект другой) {System.out.println ("Equals Method infoked!"); if (Другое == это) вернуть true; if (! (Другой экземпляр кодировщика)) вернуть false; Кодер O = (кодер) Другое; return o.name.equals (name) && o.age == age; }Затем мы строим хэшсет и помещаем объект C1 в набор:
SET <Coder> set = new Hashset <doder> (); set.add (c1);
Выполнить снова:
System.out.println (set.contains (c2));
Мы ожидаем, что содержит (C2) метод вернуть True, но на самом деле он возвращает False.
Название и возраст C1 и C2 одинаковы. Почему я звоню содержит (C2) и возвращаю ложь после того, как положил C1 в хэшсет? Это хэшкод (), который вызывает неприятности. Поскольку вы не переписываете метод HashCode (), когда Hashset ищет C2, он будет искать его в разных ведрах. Например, если C1 помещается в ведро 05, он ищет в ведре 06 при поиске C2, поэтому, конечно, его нельзя найти. Следовательно, цель нашего переписывания hashcode () состоит в том, что когда A.Equals (b) возвращает true, hashcode () a и b должен вернуть то же значение.
Спросите ли я каждый раз возвращать фиксированную номерную строку каждый раз
Кто -то может переписал это так:
@Override public int hashcode () {return 10; }Если это так, Hashmap, Hashset и другие классы сбора потеряют свое «хэш -значение». По словам <Эффективной Java>, хэш -таблица дегенератируется в связанный список. Если hashcode () каждый раз возвращает одно и то же число, то все объекты будут размещены в одном и том же ведре, и каждый раз, когда вы выполняете операцию поиска, он будет пересекать связанный список, который полностью потеряет функцию хеширования. Таким образом, лучше предоставить надежную хэшкод () как хорошую идею.
Выше приведено подробное введение этой статьи о переписывании методов HashCode () и Equals (). Я надеюсь, что это будет полезно для всех. Заинтересованные друзья могут продолжать ссылаться на другие связанные темы на этом сайте. Если есть какие -либо недостатки, пожалуйста, оставьте сообщение, чтобы указать это. Спасибо, друзья, за вашу поддержку на этом сайте!