2009-01-15 13 views
16

यह सवाल strange HashMap.put() behaviourLong.valueOf (0) .equals (Integer.valueOf (0)) क्यों गलत है?

से प्रेरित है मुझे लगता है कि मुझे समझ में क्यों Map<K,V>.put एक K लगता है लेकिन Map<K,V>.get एक Object लेता है, ऐसा नहीं कर रही है लगता है बहुत ज्यादा मौजूदा कोड टूट जाएगा।

अब हम एक बहुत ही त्रुटि प्रवण परिदृश्य में हो:

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five") 
m.contains(5); // no complains from compiler, but returns false 

यह सच लौटने अगर Long मूल्य int सीमा के भीतर किया गया था और मूल्यों के बराबर हैं द्वारा हल नहीं किया गया है सकते हैं?

उत्तर

24

यहाँ Long.java

public boolean equals(Object obj) { 
    if (obj instanceof Long) { 
     return value == ((Long)obj).longValue(); 
    } 
    return false; 
} 

अर्थात से स्रोत है इसे बराबर होने के लिए एक लंबा प्रकार होना चाहिए। वहाँ परोक्ष पूर्णांक से एक लंबे समय के लिए परिवर्तित करने के लिए कोई नियम नहीं है वस्तु प्रकार के साथ

long l = 42L 
int i = 42; 
l == i 

और अपने उदाहरण से ऊपर है कि पुरातन साथ पूर्णांक मूल्य का एक अंतर्निहित को चौड़ा करने से हो सकता है, हालांकि: मुझे लगता है के बीच मुख्य अंतर।

Java Puzzlers भी देखें, इसमें इसके बहुत सारे उदाहरण हैं।

+1

शायद मैं स्पष्ट नहीं था। मुझे पता है कि यह स्रोत कोड के रूप में क्यों होता है जो ऐसा करता है, मैंने पोस्ट करने से पहले कोड पढ़ा था। मेरा सवाल यह था कि क्यों फैसला किया गया कि यह इस तरह से होना चाहिए? –

+3

http://stackoverflow.com/questions/445990/why-is-long-valueof0-equalsinteger-valueof0-false#446911 इसे और अधिक सही तरीके से समझाता है। चूंकि समानता के लिए लंबे समय तक पूर्णांक की तुलना करने से समरूपता का उल्लंघन हो सकता है। –

5

हां, लेकिन यह सब तुलना एल्गोरिदम की तुलना में आता है और रूपांतरण कितना दूर लेता है। उदाहरण के लिए, जब आप m.Contains("5") आज़माते हैं तो आप क्या करना चाहते हैं? या यदि आप इसे पहले तत्व के रूप में 5 के साथ एक सरणी पास करते हैं? बस बोलते हुए, ऐसा लगता है कि "यदि प्रकार अलग हैं, तो चाबियाँ अलग हैं"।

फिर कुंजी के रूप में object के साथ संग्रह लें। यदि आप put5L पर 5, "5" प्राप्त करने का प्रयास करते हैं, तो आप क्या करना चाहते हैं ...? क्या होगा यदि आप put5L और 5 और "5" और आप 5F की जांच करना चाहते हैं?

चूंकि यह एक सामान्य संग्रह (या टेम्पलेटेड, या जिसे आप इसे कॉल करना चाहते हैं) है, तो इसे कुछ मूल्य प्रकारों के लिए कुछ विशेष तुलना करना होगा और करना होगा। यदि के int है तो जांचें कि ऑब्जेक्ट पास long, short, float, double, ..., फिर कनवर्ट और तुलना करें। यदि के float है तो जांच करें कि क्या ऑब्जेक्ट पास किया गया है ...

आपको पॉइंट मिलता है।

यदि कोई प्रकार मेल नहीं खाता है तो एक अपवाद फेंकना पड़ सकता था, और मैं अक्सर यह चाहता हूं।

+1

मुझे बिंदु नहीं मिलता है। निश्चित रूप से एक अंतर के बराबर लम्बाई और एक स्ट्रिंग एक इंटीजर के बराबर होने के बीच एक अंतर है? उदाहरण के लिए निहित रूपांतरण है। अपवाद ने इस मामले को पकड़ने में मदद की हो सकती है, हां। –

4

आपका प्रश्न उसके चेहरे पर उचित लगता है, लेकिन यह दो अलग-अलग प्रकारों के लिए सच होने के लिए बराबर(), यदि अनुबंध नहीं है, तो सामान्य सम्मेलनों का उल्लंघन होगा।

0

जावा भाषा डिज़ाइन का हिस्सा ऑब्जेक्ट्स के लिए कभी भी अन्य प्रकारों में परिवर्तित नहीं होना चाहिए, सी ++ के विपरीत। यह जावा को एक छोटी, सरल भाषा बनाने का हिस्सा था। सी ++ की जटिलता का एक उचित हिस्सा निहित रूपांतरण और अन्य सुविधाओं के साथ उनकी बातचीत से आता है।

इसके अलावा, जावा में प्राइमेटिव्स और ऑब्जेक्ट्स के बीच एक तेज और दृश्यमान डिचोटोमी है। यह अन्य भाषाओं से अलग है जहां यह अंतर कवर के तहत एक अनुकूलन के रूप में छिपा हुआ है। इसका मतलब है कि आप लंबे और इंटीरियर की तरह लंबे और int की तरह काम करने की उम्मीद नहीं कर सकते हैं।

लाइब्रेरी कोड इन मतभेदों को छिपाने के लिए लिखा जा सकता है, लेकिन यह वास्तव में प्रोग्रामिंग वातावरण को कम संगत बनाकर नुकसान पहुंचा सकता है।

6

आम तौर पर equals() के अनुबंध में सख्ती से व्यक्त नहीं किया गया है, वस्तुओं को खुद को किसी अन्य वस्तु के बराबर नहीं मानना ​​चाहिए जो कि समान वर्ग (यहां तक ​​कि यदि यह उप-वर्ग है) भी नहीं है। सममित संपत्ति पर विचार करें - यदि a.equals (बी) सत्य है, तो b.equals (ए) भी सत्य होना चाहिए।

के दो वस्तुओं, वर्ग Super की foo, और कक्षा Sub, जो Super फैली bar करते हैं। अब सुपर में equals() के कार्यान्वयन पर विचार करें, खासकर जब इसे foo.equals(bar) कहा जाता है। फू केवल जानता है कि बार को Object के रूप में दृढ़ता से टाइप किया गया है, इसलिए सटीक तुलना प्राप्त करने के लिए इसे सुपर की एक घटना की जांच करने की आवश्यकता है और यदि झूठी वापसी नहीं होती है। यह है, तो यह हिस्सा ठीक है। अब यह सभी इंस्टेंस फ़ील्ड इत्यादि की तुलना करता है (या जो वास्तविक तुलना कार्यान्वयन है), और उन्हें बराबर पाता है। अब तक सब ठीक है.

हालांकि, अनुबंध द्वारा यह केवल तब ही सही हो सकता है जब यह पता चलता है कि bar.equals (foo) भी सच होने वाला है। चूंकि बार सुपर का कोई उप-वर्ग हो सकता है, यह स्पष्ट नहीं है कि बराबर() विधि ओवरराइड होने जा रही है (और यदि संभवतः हो)। इस प्रकार यह सुनिश्चित करने के लिए कि आपका कार्यान्वयन सही है, आपको इसे समरूप रूप से लिखना होगा और यह सुनिश्चित करना होगा कि दोनों ऑब्जेक्ट एक ही कक्षा हैं।

अधिक मौलिक रूप से, विभिन्न वर्गों की वस्तुओं को वास्तव में बराबर नहीं माना जा सकता है - क्योंकि इस मामले में, उनमें से केवल एक को HashSet<Sub> में डाला जा सकता है, उदाहरण के लिए।

+0

लंबे और पूर्णांक विभिन्न वर्ग प्रकार हैं, और इस प्रकार उन्हें बराबर का उपयोग करके तुलना करना हमेशा झूठा होगा। –

+2

लंबे एल = 0 और इंटीजर i = 0 के लिए यदि l.equals (i) सत्य हो सकता है तो i.equals (l) भी सत्य हो सकता है। समरूपता संरक्षित है। –

+10

@dtsazza, स्टीव कुओ: मैं दृढ़ता से असहमत हूं। वास्तव में, जावा पुस्तकालय में ऐसी जगहें हैं जहां विभिन्न वर्गों की वस्तुओं को बराबर माना जाता है। उदाहरण के लिए, सूची इंटरफ़ेस निर्दिष्ट करता है कि विभिन्न वर्गों की सूचियां बराबर होंगी यदि उनकी सामग्री बराबर होती है, उनकी कक्षाओं के बावजूद; तो एक ArrayList एक लिंक्डलिस्ट के लिए .equals() हो सकता है। – newacct

0

तो तुम कोड होना चाहिए ....

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five") 
System.out.println(m.containsKey(5L)); // true 

आप भूल रहे हैं कि जावा अपने कोड autoboxing है, इसलिए उपरोक्त कोड

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five") 
System.out.println(m.containsKey(new Long(5))); // true 
System.out.println(m.containsKey(new Long(5L))); // true 

तो का एक हिस्सा करने के लिए equivelenet किया जाएगा आपके समस्या autoboxing है। दूसरा हिस्सा यह है कि आपके पास अन्य प्रकार के पोस्टर के रूप में विभिन्न प्रकार हैं।

+0

मैं autoboxing समझ में आता हूँ। मुझे संदेह है कि आपको मेरा मुद्दा नहीं मिला है, यह है कि जब कॉल करने के दौरान एल को भूलना आसान होता है। –

0

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

जब आपके पास प्राइमेटिव (और कई अन्य मामलों में) संग्रह की जीएनयू ट्रोव लाइब्रेरी का उपयोग करने की अत्यधिक अनुशंसा करते हैं। उदाहरण के लिए, एक TLongLongHashMap है जो चीजों को अंतराल के रूप में आंतरिक रूप से स्टोर करता है। नतीजतन, आप मुक्केबाजी/unboxing अंत कभी नहीं, और अप्रत्याशित व्यवहार के साथ अंत कभी नहीं:

TLongLongHashMap map = new TLongLongHashMap(); 
map.put(1L, 45L); 
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler 
int x = map.get(1); // Helpful compiler error. x is not a long 
int x = (int)map.get(1); // OK. cast reassures compiler that you know 
long x = map.get(1); // Better. 

और इतने पर। सही प्रकार प्राप्त करने की कोई आवश्यकता नहीं है, और यदि आप कुछ मूर्खतापूर्ण करते हैं (एक int में लंबे समय तक स्टोर करने का प्रयास करें) तो संकलक आपको एक त्रुटि देता है (जिसे आप सही या ओवरराइड कर सकते हैं)।

ऑटो कास्टिंग के नियमों का मतलब है कि तुलना ठीक से के रूप में अच्छी तरह से काम:

if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well 

एक बोनस के रूप में, स्मृति भूमि के ऊपर और क्रम प्रदर्शन काफी बेहतर है।

संबंधित मुद्दे