Як ми можемо прийняти рішення про найкращу реалізацію hashCode()
методу для колекції (якщо припустити, що метод рівних був замінений правильно)?
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/… )
Як ми можемо прийняти рішення про найкращу реалізацію hashCode()
методу для колекції (якщо припустити, що метод рівних був замінений правильно)?
collection.hashCode()
( hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/src/share/… )
Відповіді:
Найкраща реалізація? Це складне питання, оскільки це залежить від схеми використання.
А при майже у всіх випадках було запропоновано розумне здійснення добре в Джош Блох «s Ефективне Java в пункті 8 (друге видання). Найкраще це шукати там, бо автор там пояснює, чому підхід хороший.
Створіть a int result
і призначте нульове значення.
Для кожного f
випробуваного поляequals()
методом обчисліть хеш-код c
:
boolean
: обчислити (f ? 0 : 1)
;byte
, char
, short
або int
: обчислити (int)f
;long
: обчислити (int)(f ^ (f >>> 32))
;float
: обчислити Float.floatToIntBits(f)
;double
: обчислити Double.doubleToLongBits(f)
та обробити повернене значення, як і кожне довге значення;hashCode()
методу або 0, якщо f == null
;Поєднайте хеш-значення c
з result
:
result = 37 * result + c
Повернення result
Це повинно призвести до правильного розподілу значень хешу для більшості ситуацій використання.
Якщо ви задоволені ефективної реалізацією Java, рекомендованою dmeister, ви можете використовувати виклик бібліотеки, а не прокручувати свій власний:
@Override
public int hashCode() {
return Objects.hashCode(this.firstName, this.lastName);
}
Для цього потрібна або Guava ( com.google.common.base.Objects.hashCode
), або стандартна бібліотека на Java 7 ( java.util.Objects.hash
), але працює так само.
hashCode
це якщо у вас є звичай equals
, і саме для цього розроблені ці методи бібліотеки. Документація досить чітка щодо їх поведінки стосовно equals
. Реалізація бібліотеки не вимагає позбавлення вас від усвідомлення характеристик правильної hashCode
реалізації - ці бібліотеки полегшують вам реалізацію такої відповідної реалізації для більшості випадків, коли equals
це перекрито.
java.util.Objects.hash(...)
метод JDK7, ніж com.google.common.base.Objects.hashCode(...)
метод гуави . Я думаю, що більшість людей обрали б стандартну бібліотеку за додаткову залежність.
hashCode()
для масиву це просто його java.lang.System.identityHashCode(...)
.
Краще скористатися функціоналом, який надає Eclipse, який робить досить непогану роботу, і ви можете вкласти свої зусилля та енергію в розробку ділової логіки.
Хоча це пов’язано з Android
документацією (Wayback Machine) та Моїм власним кодом на Github , він взагалі буде працювати для Java. Моя відповідь - це розширення відповіді dmeister за допомогою простого коду, який набагато простіше читати та розуміти.
@Override
public int hashCode() {
// Start with a non-zero constant. Prime is preferred
int result = 17;
// Include a hash for each field.
// Primatives
result = 31 * result + (booleanField ? 1 : 0); // 1 bit » 32-bit
result = 31 * result + byteField; // 8 bits » 32-bit
result = 31 * result + charField; // 16 bits » 32-bit
result = 31 * result + shortField; // 16 bits » 32-bit
result = 31 * result + intField; // 32 bits » 32-bit
result = 31 * result + (int)(longField ^ (longField >>> 32)); // 64 bits » 32-bit
result = 31 * result + Float.floatToIntBits(floatField); // 32 bits » 32-bit
long doubleFieldBits = Double.doubleToLongBits(doubleField); // 64 bits (double) » 64-bit (long) » 32-bit (int)
result = 31 * result + (int)(doubleFieldBits ^ (doubleFieldBits >>> 32));
// Objects
result = 31 * result + Arrays.hashCode(arrayField); // var bits » 32-bit
result = 31 * result + referenceField.hashCode(); // var bits » 32-bit (non-nullable)
result = 31 * result + // var bits » 32-bit (nullable)
(nullableReferenceField == null
? 0
: nullableReferenceField.hashCode());
return result;
}
EDIT
Як правило, коли ви переосмислюєте hashcode(...)
, ви також хочете перекрити equals(...)
. Тож для тих, хто буде або вже реалізував equals
, ось хороша довідка від мого Github ...
@Override
public boolean equals(Object o) {
// Optimization (not required).
if (this == o) {
return true;
}
// Return false if the other object has the wrong type, interface, or is null.
if (!(o instanceof MyType)) {
return false;
}
MyType lhs = (MyType) o; // lhs means "left hand side"
// Primitive fields
return booleanField == lhs.booleanField
&& byteField == lhs.byteField
&& charField == lhs.charField
&& shortField == lhs.shortField
&& intField == lhs.intField
&& longField == lhs.longField
&& floatField == lhs.floatField
&& doubleField == lhs.doubleField
// Arrays
&& Arrays.equals(arrayField, lhs.arrayField)
// Objects
&& referenceField.equals(lhs.referenceField)
&& (nullableReferenceField == null
? lhs.nullableReferenceField == null
: nullableReferenceField.equals(lhs.nullableReferenceField));
}
Спершу переконайтеся, що рівність реалізована правильно. З статті IBM DeveloperWorks :
- Симетрія: для двох посилань, a і b, a.equals (b), якщо і тільки якщо b.equals (a)
- Рефлексивність: для всіх ненульових посилань, a.equals (a)
- Транзитивність: Якщо a.equals (b) і b.equals (c), то a.equals (c)
Потім переконайтесь, що їхнє відношення до hashCode поважає контакт (з тієї ж статті):
- Узгодженість з hashCode (): Два рівні об'єкти повинні мати однакове значення hashCode ()
Нарешті, хороша хеш-функція повинна прагнути наближатися до ідеальної хеш-функції .
about8.blogspot.com, ви сказали
якщо equals () повертає true для двох об'єктів, то hashCode () повинен повернути те саме значення. Якщо equals () повертає false, то hashCode () повинен повертати різні значення
Я не можу погодитися з тобою. Якщо два об'єкти мають один і той же хеш-код, це не означає, що вони рівні.
Якщо A дорівнює B, то A.hashcode повинен дорівнювати B.hascode
але
якщо A.hashcode дорівнює B.hascode, це не означає, що A повинен дорівнювати B
(A != B) and (A.hashcode() == B.hashcode())
це, ми називаємо зіткнення хеш-функцій. Це тому, що кодомейн хеш-функції завжди обмежений, тоді як домен зазвичай - ні. Чим більший кодомен, тим рідше має відбуватися зіткнення. Хороші хеш-функції повинні повертати різні хеші для різних об'єктів з найбільшою можливою можливістю з огляду на конкретний розмір кодомена. Однак це рідко може бути повністю гарантованим.
Якщо ви використовуєте затемнення, ви можете генерувати equals()
та hashCode()
використовувати:
Джерело -> Створити хеш-код () та дорівнює ().
За допомогою цієї функції ви можете визначити, які поля ви хочете використовувати для рівності та обчислення хеш-коду, а Eclipse генерує відповідні методи.
Там хороша реалізація з Ефективне Java «s hashcode()
і equals()
логіки в Apache Commons Lang . Оформити замовлення HashCodeBuilder та EqualsBuilder .
Objects
клас надає hash(Object ..args)
та equals()
методи від Java7 далі. Вони рекомендовані для будь-яких додатків, які використовують jdk 1.7+
IdentityHashMap
). FWIW Я використовую хеш-код на основі id і дорівнює для всіх об'єктів.
Просто коротка примітка для заповнення іншої більш детальної відповіді (в терміні коду):
Якщо я розглядаю питання про те, як зробити-я-створити-хеш-таблицю в java, і, особливо, питання про jGuru FAQ , я вважаю, що деякі інші критерії, за якими можна судити хеш-код, такі:
Якщо я правильно розумію ваше запитання, у вас є власний клас колекції (тобто новий клас, який поширюється на інтерфейс колекції), і ви хочете реалізувати метод hashCode ().
Якщо ваш колекційний клас поширюється на AbstractList, вам не доведеться турбуватися про це, вже існує реалізація рівнянь () та хеш-коду (), яка працює за допомогою ітерації всіх об'єктів та додавання їх хеш-кодів () разом.
public int hashCode() {
int hashCode = 1;
Iterator i = iterator();
while (i.hasNext()) {
Object obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
Тепер, якщо те, що ви хочете, є найкращим способом обчислити хеш-код для конкретного класу, я зазвичай використовую оператор ^ (побітовий ексклюзив або) для обробки всіх полів, які я використовую методом equals:
public int hashCode(){
return intMember ^ (stringField != null ? stringField.hashCode() : 0);
}
@ about8: там досить серйозна помилка.
Zam obj1 = new Zam("foo", "bar", "baz");
Zam obj2 = new Zam("fo", "obar", "baz");
той же хеш-код
ти, мабуть, хочеш чогось подібного
public int hashCode() {
return (getFoo().hashCode() + getBar().hashCode()).toString().hashCode();
(чи можете ви отримати хеш-код прямо з int на Java в наші дні? Я думаю, що він робить деякий автопередачу .. якщо це так, пропустіть до toString, це некрасиво.)
foo
і bar
призводить до того ж hashCode
. Ваш toString
AFAIK не збирає, і якщо це станеться, то це жахливо неефективно. Щось подібне 109 * getFoo().hashCode() + 57 * getBar().hashCode()
швидше, простіше і не створює зайвих зіткнень.
Використовуйте методи відображення на Apache Commons EqualsBuilder та HashCodeBuilder .
Я використовую крихітну обгортку навколо, Arrays.deepHashCode(...)
оскільки вона обробляє масиви, подані як параметри правильно
public static int hash(final Object... objects) {
return Arrays.deepHashCode(objects);
}
будь-який метод хешування, який рівномірно розподіляє значення хеша за можливий діапазон, є хорошою реалізацією. Див ефективної Java ( http://books.google.com.au/books?id=ZZOiqZQIbRMC&dq=effective+java&pg=PP1&ots=UZMZ2siN25&sig=kR0n73DHJOn-D77qGj0wOxAxiZw&hl=en&sa=X&oi=book_result&resnum=1&ct=result ), є хороший наконечник там для реалізації хеш-коду (пункт 9, я думаю ...).
Ось ще одна демонстрація підходу JDK 1.7+ з логікою суперкласу. Я вважаю це досить сприятливим для облікового запису hashCode () класу Object, чистою залежністю від JDK і без зайвих ручних робіт. Будь ласка, запишиObjects.hash()
це недійсне.
Я не включаю жодної equals()
реалізації, але насправді вона вам, звичайно, знадобиться.
import java.util.Objects;
public class Demo {
public static class A {
private final String param1;
public A(final String param1) {
this.param1 = param1;
}
@Override
public int hashCode() {
return Objects.hash(
super.hashCode(),
this.param1);
}
}
public static class B extends A {
private final String param2;
private final String param3;
public B(
final String param1,
final String param2,
final String param3) {
super(param1);
this.param2 = param2;
this.param3 = param3;
}
@Override
public final int hashCode() {
return Objects.hash(
super.hashCode(),
this.param2,
this.param3);
}
}
public static void main(String [] args) {
A a = new A("A");
B b = new B("A", "B", "C");
System.out.println("A: " + a.hashCode());
System.out.println("B: " + b.hashCode());
}
}
Стандартна реалізація є слабкою і використання її призводить до зайвих зіткнень. Уявіть собі
class ListPair {
List<Integer> first;
List<Integer> second;
ListPair(List<Integer> first, List<Integer> second) {
this.first = first;
this.second = second;
}
public int hashCode() {
return Objects.hashCode(first, second);
}
...
}
Тепер,
new ListPair(List.of(a), List.of(b, c))
і
new ListPair(List.of(b), List.of(a, c))
мають те саме hashCode
, а саме 31*(a+b) + c
як множник, який використовується дляList.hashCode
повторного використання тут. Очевидно, що зіткнення неминучі, але створювати непотрібні зіткнення просто ... марно.
Немає нічого принципово розумного у використанні 31
. Мультиплікатор повинен бути непарним, щоб уникнути втрати інформації (будь-який парний множник втрачає хоча б найзначніший біт, кратні чотири втрачають два тощо). Будь-який непарний множник може бути використаний. Невеликі множники можуть призвести до більш швидких обчислень (JIT може використовувати зрушення та доповнення), але враховуючи, що у сучасних Intel / AMD затримка має затримку лише на три цикли, це навряд чи має значення. Невеликі множники також призводять до більшого зіткнення для невеликих входів, що іноді може бути проблемою.
Використовувати прайм безглуздо, оскільки прайми не мають значення в кільці Z / (2 ** 32).
Тож я б рекомендував використовувати випадкову велику непарну кількість (не соромтеся брати участь у виграші). Оскільки процесори i86 / amd64 можуть використовувати більш коротку інструкцію для операндів, що знаходяться в одному підписаному байті, для множників типу 109 є невелика перевага швидкості, як мінімум. Для мінімізації зіткнень візьміть щось на зразок 0x58a54cf5.
Використання різних множників у різних місцях є корисним, але, ймовірно, недостатньо для виправдання додаткової роботи.
При комбінуванні значень хешу я зазвичай використовую метод комбінування, який використовується в бібліотеці boost c ++, а саме:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
Це робить досить непогану роботу щодо забезпечення рівномірного розподілу. Деякі дискусії про те, як працює ця формула, дивіться у публікації StackOverflow: Магічне число в boost :: hash_combine
Існує хороша дискусія про різні хеш-функції на веб-сайті: http://burtleburtle.net/bob/hash/doobs.html
Для простого класу часто найпростіше реалізувати hashCode () на основі полів класу, які перевіряються реалізацією equals ().
public class Zam {
private String foo;
private String bar;
private String somethingElse;
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Zam otherObj = (Zam)obj;
if ((getFoo() == null && otherObj.getFoo() == null) || (getFoo() != null && getFoo().equals(otherObj.getFoo()))) {
if ((getBar() == null && otherObj. getBar() == null) || (getBar() != null && getBar().equals(otherObj. getBar()))) {
return true;
}
}
return false;
}
public int hashCode() {
return (getFoo() + getBar()).hashCode();
}
public String getFoo() {
return foo;
}
public String getBar() {
return bar;
}
}
Найважливіше - дотримуватися hashCode () та equals () послідовним: якщо equals () повертає true для двох об'єктів, то hashCode () повинен повертати те саме значення. Якщо equals () повертає false, то hashCode () повинен повертати різні значення.
("abc"+""=="ab"+"c"=="a"+"bc"==""+"abc")
. Це важка вада. Було б краще оцінити хеш-код для обох полів, а потім обчислити лінійну комбінацію їх (бажано, використовуючи прайми в якості коефіцієнтів).
foo
і bar
спричиняє непотрібне зіткнення.
Objects.hashCode(collection)
має бути ідеальним рішенням!