Справжня відповідь на
чому є 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методи здаються виправданими віддаленими сучасними найкращими методами.