जावा: संग्रह एक तुलनित्र को क्यों स्वीकार करते हैं, लेकिन (काल्पनिक) हैसर और भूमध्य रेखा को नहीं?


25

यह मुद्दा सबसे स्पष्ट है जब आपके पास इंटरफ़ेस के विभिन्न कार्यान्वयन होते हैं, और किसी विशेष संग्रह के प्रयोजनों के लिए आप केवल ऑब्जेक्ट के इंटरफ़ेस-स्तरीय दृश्य के बारे में परवाह करते हैं। उदाहरण के लिए, मान लीजिए कि आपके पास एक इंटरफ़ेस था:

public interface Person {
    int getId();
}

लागू करने hashcode()और equals()कक्षाओं को लागू करने का सामान्य तरीका इस तरह से कोड होगा equals:

if (getClass() != other.getClass()) {
    return false;
}

यह समस्याओं का कारण बनता है जब आप Personएक में कार्यान्वयन को मिलाते हैं HashMap। यदि HashMapकेवल इंटरफ़ेस-स्तरीय दृश्य के बारे में परवाह है Person, तो यह डुप्लिकेट के साथ समाप्त हो सकता है जो केवल उनके कार्यान्वयन कक्षाओं में भिन्न होते हैं।

आप equals()सभी कार्यान्वयनों के लिए एक ही उदार विधि का उपयोग करके इस मामले को काम कर सकते हैं , लेकिन फिर आप equals()एक अलग संदर्भ में गलत काम करने का जोखिम चलाते हैं (जैसे दो Personएस की तुलना करना जो संस्करण संख्याओं के साथ डेटाबेस रिकॉर्ड द्वारा समर्थित हैं)।

मेरा अंतर्ज्ञान मुझे बताता है कि समानता को प्रति वर्ग के बजाय प्रति संग्रह के रूप में परिभाषित किया जाना चाहिए। आदेशों पर भरोसा करने वाले संग्रह का उपयोग करते समय, आप Comparatorप्रत्येक संदर्भ में सही क्रम चुनने के लिए एक कस्टम का उपयोग कर सकते हैं । हैश-आधारित संग्रहों के लिए कोई एनालॉग नहीं है। ऐसा क्यों है?

बस स्पष्ट करने के लिए, यह प्रश्न " इंटरफ़ेस में .compareTo () क्यों है? से भिन्न है। जबकि (। जावा में जावा वर्ग में है? ") क्योंकि यह संग्रह के कार्यान्वयन से संबंधित है। compareTo()और equals()/ hashcode()दोनों संग्रह का उपयोग करते समय सार्वभौमिकता की समस्या से ग्रस्त हैं: आप विभिन्न संग्रह के लिए अलग-अलग तुलना कार्यों को नहीं चुन सकते हैं। तो इस सवाल के प्रयोजनों के लिए, किसी वस्तु का वंशानुगत पदानुक्रम बिल्कुल भी मायने नहीं रखता है; यह सब मायने रखता है कि तुलना फ़ंक्शन प्रति-ऑब्जेक्ट या प्रति-संग्रह में परिभाषित है या नहीं।


5
आप हमेशा Personअपेक्षित equalsऔर hashCodeव्यवहार को लागू करने के लिए रैपर वस्तुओं को पेश कर सकते हैं । तुम तो एक होगा HashMap<PersonWrapper, V>। यह एक उदाहरण है जहां एक शुद्ध-ओओपी दृष्टिकोण सुरुचिपूर्ण नहीं है: किसी वस्तु पर प्रत्येक ऑपरेशन उस वस्तु की विधि के रूप में समझ में नहीं आता है। जावा के पूरे Objectप्रकार विभिन्न जिम्मेदारियों का एक मिश्रण है - केवल getClass, finalizeऔर toStringतरीकों आज के सर्वोत्तम प्रथाओं से दूर से न्यायोचित लगते हैं।
आमोन

1
1) C # में आप IEqualityComparer<T>हैश आधारित संग्रह के लिए एक पास कर सकते हैं । यदि आप एक निर्दिष्ट नहीं करते हैं, तो यह एक डिफ़ॉल्ट कार्यान्वयन का उपयोग करता है Object.Equalsऔर उसके आधार पर Object.GetHashCode()। 2) Equalsएक परस्पर संदर्भ प्रकार पर ओवरराइडिंग IMO शायद ही कभी एक अच्छा विचार है। इस तरह डिफ़ॉल्ट समानता बहुत सख्त है, लेकिन जब आप एक कस्टम के माध्यम से इसकी आवश्यकता होती है, तो आप अधिक आराम समानता नियम का उपयोग कर सकते हैं IEqualityComparer<T>
कोडइन्चोस

जवाबों:


23

इस डिजाइन को कभी-कभी "सार्वभौमिक समानता" के रूप में जाना जाता है, यह धारणा है कि दो चीजें समान हैं या नहीं एक सार्वभौमिक संपत्ति है।

क्या अधिक है, समानता दो वस्तुओं की एक संपत्ति है , लेकिन OO में, आप हमेशा एक एकल ऑब्जेक्ट पर एक विधि कहते हैं , और वह वस्तु पूरी तरह से यह तय करती है कि उस विधि कॉल को कैसे संभालना है। तो, जावा की तरह एक डिजाइन में, जहां समानता दो वस्तुओं में से एक की संपत्ति है, जिसकी तुलना समानता के कुछ बुनियादी गुणों जैसे कि समरूपता ( a == bb == a) की गारंटी देना संभव नहीं है , क्योंकि पहले मामले में, विधि को बुलाया जा रहा है aऔर दूसरे मामले में इसे बुलाया जा रहा है bऔर OO के मूल सिद्धांतों के कारण, यह पूरी तरह से aनिर्णय है (पहले मामले में) याbनिर्णय (दूसरे मामले में) चाहे वह खुद को दूसरे के बराबर समझता हो या नहीं। समरूपता हासिल करने का एकमात्र तरीका दो वस्तुओं का सहयोग है, लेकिन अगर वे ... कठिन भाग्य नहीं है।

एक समाधान यह होगा कि समानता किसी एक वस्तु की संपत्ति नहीं है, लेकिन या तो दो वस्तुओं की संपत्ति है, या तीसरी वस्तु की संपत्ति है। वह बाद वाला विकल्प भी सार्वभौमिक समानता की समस्या को हल करता है, क्योंकि यदि आप समानता को तीसरे "संदर्भ" ऑब्जेक्ट की संपत्ति बनाते हैं, तो आप EqualityComparerविभिन्न संदर्भों के लिए अलग- अलग ऑब्जेक्ट होने की कल्पना कर सकते हैं ।

यह है डिजाइन उदाहरण के लिए, हास्केल के लिए चुना, साथ Eqtypeclass। यह कुछ तृतीय-पक्ष स्काला लाइब्रेरी (उदाहरण के लिए स्कालाज़) द्वारा चुना गया डिज़ाइन है, लेकिन स्काला कोर या मानक पुस्तकालय नहीं है, जो अंतर्निहित होस्ट प्लेटफ़ॉर्म के साथ संगतता के लिए सार्वभौमिक समानता का उपयोग करता है।

यह दिलचस्प है, जावा के इंटरफेस Comparable/ Comparatorइंटरफेस के साथ चुना गया डिज़ाइन भी । जावा के डिजाइनर स्पष्ट रूप से समस्या के बारे में जानते थे, लेकिन किसी कारण से इसे केवल आदेश देने के लिए हल किया गया था, लेकिन समानता (या हैशिंग) के लिए नहीं।

तो, सवाल के रूप में

क्यों एक Comparatorइंटरफ़ेस है, लेकिन नहीं Hasherऔर Equator?

जवाब है "मुझे नहीं पता"। स्पष्ट रूप से, जावा के डिजाइनरों को समस्या के बारे में पता था, जैसा कि इसके अस्तित्व से Comparatorस्पष्ट है, लेकिन उन्होंने स्पष्ट रूप से इसे समानता और हैशिंग के लिए समस्या नहीं माना । अन्य भाषाओं और पुस्तकालयों में अलग-अलग विकल्प हैं।


7
+1, लेकिन ध्यान दें कि ओओ भाषाएं हैं जहां कई प्रेषण मौजूद हैं (स्मॉलटाक, कॉमन लिस्प)। तो हमेशा निम्नलिखित वाक्य में बहुत मजबूत है: "ओओ में, आप हमेशा एक ही वस्तु पर एक विधि कहते हैं"।
coredump

मुझे वह बोली मिली है जिसकी मुझे तलाश थी; JLS 1.0 के अनुसार The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable, अर्थात दोनों को equalsऔर जावा के द्वारा विधियों के hashCodeरूप में पूरी तरह से पेश किया गया था - इसमें कहीं भी UE या कुछ भी सिलेमर की कोई कल्पना नहीं है, और उद्धरण मेरे लिए पर्याप्त स्पष्ट है; यदि के लिए नहीं , तो शायद एक इंटरफ़ेस में होता । इस तरह, जब मैं पूर्व में आपके उत्तर को सही मानता था, अब मैं इसे असंतुलित मानता हूं। ObjectHashtableHashtableequalsComparable
वैक्सक्विस

@ JörgWMittag यह एक टाइपो, IFTFY था। BTW, का बोलना clone- यह मूल रूप से एक ऑपरेटर था , न कि एक विधि (ओक भाषा विनिर्देश देखें), बोली: The unary operator clone is applied to an object. (...) The clone operator is normally used inside new to clone the prototype of some class, before applying the initializers (constructors)- तीन कीवर्ड जैसे ऑपरेटर थे instanceof new clone(धारा 8.1, ऑपरेटर)। मुझे लगता है कि यह clone/ Cloneableगड़बड़ का वास्तविक (ऐतिहासिक) कारण है - Cloneableबस एक बाद का आविष्कार था, और मौजूदा cloneकोड इसके साथ रेट्रोफिट था।
वैक्सक्विस

2
"यह हास्केल के लिए चुना गया डिज़ाइन है, उदाहरण के लिए, ईक टाइपकास्ट के साथ" यह सच की तरह है, लेकिन यह ध्यान देने योग्य है कि हास्केल स्पष्ट रूप से सामने बताता है कि विभिन्न प्रकार की दो वस्तुएं कभी भी समान नहीं हैं, जबकि जावा का दृष्टिकोण नहीं है। समानता आपरेशन इस प्रकार का हिस्सा है प्रकार , (अतः "typeclass") एक तिहाई संदर्भ मूल्य का हिस्सा नहीं।
जैक

19

इसका असली जवाब

क्यों एक Comparatorइंटरफ़ेस है, लेकिन नहीं Hasherऔर Equator?

जोश बलोच के सौजन्य से बोली :

मूल जावा एपीआई एक समापन बाजार की खिड़की को पूरा करने के लिए एक तंग समय सीमा के तहत बहुत जल्दी किया गया था। मूल जावा टीम ने एक अविश्वसनीय काम किया, लेकिन सभी एपीआई परिपूर्ण नहीं हैं।

यह समस्या पूरी तरह से जावा के इतिहास में निहित है, अन्य समान मामलों, जैसे .clone()बनाम Cloneable

tl; डॉ

यह ऐतिहासिक कारणों से मुख्य रूप से है; वर्तमान व्यवहार / अमूर्तता को JDK 1.0 में पेश किया गया था और बाद में तय नहीं किया गया था क्योंकि पिछड़े कोड संगतता को बनाए रखने के साथ ऐसा करना लगभग असंभव था।


सबसे पहले, जाने-माने जावा तथ्यों के एक जोड़े को जोड़ते हैं:

  1. जावा, आज की शुरुआत से ही, गर्व से पीछे की ओर संगत था, विरासत एपीआई की आवश्यकता अभी भी नए संस्करणों में समर्थित है,
  2. जैसे, JDK 1.0 के साथ पेश की गई लगभग हर भाषा का निर्माण वर्तमान दिन तक रहता था,
  3. Hashtable, .hashCode()और .equals()JDK 1.0 में लागू किया गया, ( हैशटेबल )
  4. Comparable/ ComparatorJDK 1.2 ( तुलनीय ) में पेश किया गया था ,

अब, यह इस प्रकार है:

  1. वस्तुतः असंभव था .hashCode()और .equals()पीछे हटने के लिए और अलग-अलग इंटरफेस के प्रति संवेदनहीन और संवेदनाहीन था, जबकि लोगों को एहसास होने के बाद भी पीछे संगतता बनाए रखना बेहतर था क्योंकि उन्हें सुपरबॉजेक्ट में डाल दिया गया था, क्योंकि 1.2 द्वारा प्रत्येक जावा प्रोग्रामर को पता था कि हर Objectकोई उनके पास है, और उनके पास था संकलित कोड (जेवीएम) संगतता प्रदान करने के लिए शारीरिक रूप से वहां रहने के लिए - और प्रत्येक Objectउपवर्ग में एक स्पष्ट इंटरफ़ेस जोड़ना जो वास्तव में उन्हें लागू करते हैं, इस गड़बड़ को समान (sic!) Clonableएक के रूप में करेंगे ( बलोच चर्चा करता है कि क्लोन योग्य क्यों , उदाहरण के लिए EJ 2nd में भी चर्चा की गई है! और SO सहित कई अन्य स्थान),
  2. वे सिर्फ उन्हें भविष्य की पीढ़ी के लिए डब्ल्यूटीएफ के निरंतर स्रोत के लिए वहां छोड़ गए हैं।

अब, आप पूछ सकते हैं कि " Hashtableइस सब के साथ क्या है "?

इसका उत्तर है: 1995/1996 में कोर जावा डेवलपर्स के अनुबंध hashCode()/equals() और न ही इतनी अच्छी भाषा डिजाइन कौशल।

जावा से भाषा 1.0 बोली , दिनांक 1996 - 4.3.2 द क्लास Object, पृष्ठ 4:

हैशटैब जैसे (are21.7) के लाभ के लिए तरीके equalsऔर घोषणाएं की जाती हैं। विधि के बराबर वस्तु समानता की धारणा को परिभाषित करता है, जो मूल्य पर आधारित है, संदर्भ नहीं, तुलना।hashCodejava.util.Hashtable

(ध्यान दें कि इस सटीक कथन को बाद के संस्करणों में बदल दिया गया है , कहने के लिए, उद्धरण: ऐतिहासिक जेएलएस को पढ़े बिना The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.सीधे Hashtable- hashCode- equalsसंबंध बनाना असंभव बना देता है !)

जावा टीम ने फैसला किया कि वे एक अच्छा शब्दकोश-शैली संग्रह चाहते हैं, और उन्होंने Hashtable(अब तक अच्छा विचार) बनाया है, लेकिन वे चाहते थे कि प्रोग्रामर इसे कम से कम कोड / लर्निंग कर्व के रूप में उपयोग कर सकें (उफ़! आने वाली परेशानी!) - और, चूंकि अभी तक कोई जेनेरिक नहीं था [यह जेडीके 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ओवरराइड नहीं है, या आप जीसी लाखों Objectअपने आवेदन के जीवन चक्र के दौरान रों 1 ) , यह कहना सुरक्षित है कि वे मुख्य रूप से एक बैकअप उपाय और उपयोग की सुविधा के लिए प्रदान किए गए थे। यह वास्तव में हमें ज्ञात तथ्य से मिलता है जो हमेशा दोनों को ओवरराइड करता है .equals()और .hashCode()यदि आप वास्तव में वस्तुओं की तुलना करना चाहते हैं या हैश-स्टोर कर रहे हैं। उनमें से केवल एक को दूसरे के बिना ओवरराइड करना आपके कोड को खराब करने का एक अच्छा तरीका है (दुष्ट परिणामों की तुलना करें या अत्यधिक उच्च बाल्टी टकराव के मूल्यों से) - और इसके चारों ओर अपना सिर प्राप्त करना शुरुआती लोगों के लिए निरंतर भ्रम और त्रुटियों का स्रोत है (खोज एसओ को देखने के लिए) अपने लिए) और अधिक अनुभवी लोगों के लिए निरंतर उपद्रव।

यह भी ध्यान दें, हालांकि C # समान तरीके से समान और हैशकोड के साथ काम करता है, एरिक लिपर्ट खुद कहते हैं कि उन्होंने C # के साथ लगभग वही गलती की जो Sun ने जावा के साथ C # स्थापना के पहले की थी :

लेकिन ऐसा क्यों होना चाहिए कि हर वस्तु को हैश टेबल में डालने के लिए खुद को हैश करने में सक्षम होना चाहिए? हर वस्तु को करने में सक्षम होने के लिए एक अजीब चीज की तरह लगता है। मुझे लगता है कि अगर हम आज स्क्रैच से टाइप सिस्टम को नया रूप दे रहे हैं, तो हैशिंग को अलग तरीके से किया जा सकता है, शायद एक IHashableइंटरफेस के साथ । लेकिन जब सीएलआर प्रकार प्रणाली डिजाइन की गई थी तो कोई सामान्य प्रकार नहीं थे और इसलिए किसी भी वस्तु को संग्रहीत करने में सक्षम होने के लिए एक सामान्य-उद्देश्य वाली हैश तालिका की आवश्यकता थी।

1 बेशक, Object#hashCodeअभी भी टकरा सकता है, लेकिन इसे करने के लिए थोड़ा प्रयास करना पड़ता है, देखें: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 और विवरणों से जुड़ी बग रिपोर्ट; /programming/1381060/hashcode-uniqueness/1381114#1381114 इस विषय को और अधिक गहराई से शामिल करता है।


यह सिर्फ जावा नहीं है, हालांकि। इसके कई समकालीन (रूबी, पायथन,…) और पूर्ववर्ती (स्मॉलटाक,…) और इसके कुछ उत्तराधिकारियों के पास यूनिवर्सल इक्वेलिटी और यूनिवर्सल हैशबिलिटी (क्या यह एक शब्द है?)।
जोर्ग डब्ल्यू मित्तग

@ JörgWMittag प्रोग्रामर को देखें ।stackexchange.com/questions/283194/… - मैं जावा में "यूई" के बारे में असहमत हूं; यूई ऐतिहासिक रूप से कभी भी वास्तविक चिंता का विषय नहीं था Object; हैशबिलिटी थी।
vaxquis

@vaxview मैं इस पर वीणा नहीं करना चाहता, लेकिन मेरी पिछली टिप्पणी से पता चलता है कि दो एक साथ उपलब्ध वस्तुओं में समान (डिफ़ॉल्ट) हैश कोड हो सकता है।
मोनिका

1
@vaxquis ठीक है। मैं वह खरीदता हूं। मेरी चिंता यह है कि जो कोई सीख रहा है, वह इसे देखेगा और सोचेगा कि बराबरी आदि के बजाय सिस्टम हैशकोड का उपयोग करके वे चतुर हैं। यदि वे ऐसा करते हैं, तो यह संभवतया पर्याप्त रूप से अच्छी तरह से काम करेगा सिवाय दुर्लभ समय के और यह नहीं होगा मज़बूती से मुद्दे को पुन: पेश करने का कोई तरीका नहीं।
जिमीजैम

1
यह स्वीकृत उत्तर होना चाहिए, क्योंकि स्वीकृत उत्तर का निष्कर्ष "मैं नहीं जानता"
फीनिक्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.