Справжня відповідь на
чому є Comparator
інтерфейс, але немає Hasher
і Equator
?
є, цитувати люб'язно Джоша Блоха :
Оригінальні API API Java були зроблені дуже швидко в стислі терміни, щоб зустріти закриття вікна ринку. Оригінальна команда Java зробила неймовірну роботу, але не всі API є ідеальними.
Проблема полягає тільки в історії Яви, як і з іншими подібними питаннями, наприклад , .clone()
проти Cloneable
.
тл; д-р
це в основному з історичних причин; Поточна поведінка / абстракція була введена в JDK 1.0 і не була виправлена згодом, оскільки це було практично неможливо зробити при підтримці сумісності із зворотним кодом.
Спочатку підведемо підсумки декількох відомих фактів Java:
- Java з самого початку до сьогодні була гордо сумісною, вимагаючи, щоб застарілі API підтримувались у нових версіях,
- як такий, майже кожна мовна конструкція, представлена JDK 1.0, жила донині,
Hashtable
, .hashCode()
та .equals()
були реалізовані в JDK 1.0, ( Hashtable )
Comparable
/ Comparator
був представлений у JDK 1.2 ( Порівняно ),
Тепер випливає:
- модернізувати
.hashCode()
та .equals()
розрізнити інтерфейси було практично неможливо і безглуздо, зберігаючи сумісність із зворотним бажанням після того, як люди зрозуміли, що є кращі абстракції, ніж ставити їх у суперпроект, тому що, наприклад, кожен програміст Java на 1,2 знав, що кожен Object
має їх, і вони мали залишатися там фізично, щоб забезпечити сумісність компільованого коду (JVM) - і додавання явного інтерфейсу до кожного Object
підкласу, який реально їх реалізував, зробить цей безлад рівним (sic!) Clonable
одному ( Bloch обговорює, чому Cloneable смокче , також обговорюється, наприклад, у EJ 2nd та багато інших місць, включаючи SO),
- вони просто залишили їх там, щоб майбутнє покоління було постійним джерелом WTF.
Тепер ви можете запитати "що Hashtable
має все це"?
Відповідь: hashCode()
/ equals()
контракт і не дуже хороші навички мовного дизайну основних розробників Java у 1995/1996 роках.
Цитата з мовної специфікації Java 1.0, від 1996 р. - 4.3.2 Клас Object
, стор.41:
Методи equals
і hashCode
декларуються на користь хеш- java.util.Hashtable
файлів, таких як (§21.7). Метод дорівнює визначає поняття об’єктної рівності, яке базується на значенні, а не посиланні, порівнянні.
(зауважте, що це точне твердження було змінено в пізніших версіях, скажімо, цитата:, що The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.
робить неможливим встановлення прямого Hashtable
- hashCode
- equals
з'єднання без читання історичних JLS!)
Команда Java вирішила, що хоче гарну колекцію стильового словника, і вони створили Hashtable
(хороша ідея поки що), але вони хотіли, щоб програміст міг використовувати її з якомога меншою кривою коду / навчання (на жаль! Проблеми з вхідними!) - і, оскільки ще не було жодної генерики (все-таки JDK 1.0), це означатиме, що кожен Object
з Hashtable
них повинен явно реалізовувати якийсь інтерфейс (а інтерфейси тоді ще були лише на початку) ... Comparable
ще немає!) , що робить це стримуючим фактором, щоб використовувати його для багатьох - або Object
доведеться неявно реалізувати якийсь метод хешування.
Очевидно, вони йшли з рішенням 2 з причин, зазначених вище. Так, тепер ми знаємо, що вони помилялися. ... легко бути розумним заднім числом. глузувати
Тепер, hashCode()
вимагає, щоб кожен об'єкт, що має його, повинен мати чіткий equals()
метод - тому цілком очевидно, що equals()
його потрібно було також вкласти Object
.
Оскільки по замовчуванням реалізацій цих методів по дійсним a
і b
Object
з, за суті марна, будучи надлишковими (робить a.equals(b)
рівними для a==b
і a.hashCode() == b.hashCode()
приблизно рівні по a==b
також, якщо hashCode
і / або equals
не перекриває, або GC сотні тисяч Object
з в протягом всього життєвого циклу додатки 1 ) , можна з упевненістю сказати, що вони надавалися в основному як міра резервного копіювання та для зручності використання. Саме так ми доходимо до загальновідомого факту, який завжди переосмислює обидва, .equals()
і .hashCode()
якщо ви маєте намір насправді порівнювати об'єкти або зберігати їх хеш-пам'ять. Переосмислення лише одного з них без іншого - це хороший спосіб накрутити свій код (за злісні порівняння результатів або шалено високі значення зіткнення ковша) - і обернути голову навколо цього - джерело постійної плутанини та помилок для початківців (шукайте ТАК, щоб побачити це для себе) і постійна неприємність до більш досвідчених.
Також зауважте, що хоча C # має справу з рівними та хеш-кодами дещо кращим чином, сам Ерік Ліпперт заявляє, що вони зробили майже таку саму помилку з C #, що Sun робила з Java за роки до початку C # :
Але чому має бути так, що кожен об'єкт повинен мати можливість хешувати себе для вставки в хеш-таблицю? Здається, що це дивна річ, що вимагає, щоб кожен об’єкт міг це зробити. Я думаю, якби ми сьогодні переробляли систему типів з нуля, хешування може бути зроблено інакше, можливо, з IHashable
інтерфейсом. Але коли система CLR була розроблена, не було загальних типів, а тому хеш-таблиця загального призначення, необхідна для зберігання будь-якого об'єкта.
1 , звичайно, Object#hashCode
все ще може вступити в протиріччя, але це займає трохи зусиль , щоб зробити це, см: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 і пов'язані повідомлення про помилки для деталей; /programming/1381060/hashcode-uniqueness/1381114#1381114 висвітлює цю тему більш глибоко.
Person
щоб реалізувати очікуваніequals
таhashCode
поведінку. Тоді ви мали бHashMap<PersonWrapper, V>
. Це один із прикладів, коли підхід «чистого OOP» не є елегантним: не кожна операція над об’єктом має сенс як метод цього об’єкта. ВесьObject
тип Java - це сукупність різних обов'язків - лише методиgetClass
,finalize
іtoString
методи здаються виправданими віддаленими сучасними найкращими методами.