क्या कारण हैं कि Map.get (ऑब्जेक्ट कुंजी) नहीं है (पूरी तरह से) सामान्य


405

के इंटरफ़ेस में पूरी तरह से सामान्य प्राप्त विधि न करने के निर्णय के पीछे क्या कारण हैं java.util.Map<K, V>

प्रश्न को स्पष्ट करने के लिए, विधि का हस्ताक्षर है

V get(Object key)

के बजाय

V get(K key)

और मैं सोच रहा हूँ क्यों (एक ही बात के लिए remove, containsKey, containsValue)।


3
संग्रह के बारे में इसी तरह का प्रश्न: stackoverflow.com/questions/104799/…
AlikElzin-kilaka


1
गजब का। मैं 20+ वर्षों से जावा का उपयोग कर रहा हूं, और आज मुझे इस समस्या का एहसास है।
घोस्टकट

जवाबों:


260

जैसा कि दूसरों ने उल्लेख किया है, इसका कारण get(), आदि सामान्य नहीं है क्योंकि आप जिस प्रविष्टि को पुनः प्राप्त कर रहे हैं उसकी कुंजी उसी प्रकार की नहीं है जिस वस्तु को आप पास करते हैं get(); विधि के विनिर्देशन के लिए केवल यह आवश्यक है कि वे समान हों। यह इस प्रकार से है कि equals()विधि ऑब्जेक्ट में पैरामीटर के रूप में कैसे ले जाती है, न कि ऑब्जेक्ट के समान प्रकार।

यद्यपि यह आमतौर पर सही हो सकता है कि कई वर्गों ने equals()परिभाषित किया है ताकि इसकी वस्तुएं केवल अपने वर्ग की वस्तुओं के बराबर हो सकें, जावा में कई स्थान हैं जहां यह मामला नहीं है। उदाहरण के लिए, विनिर्देशन List.equals()कहता है कि दो सूची वस्तुएँ समान हैं यदि वे दोनों सूचियाँ हैं और समान सामग्री हैं, भले ही वे अलग-अलग कार्यान्वयन हों List। इसलिए इस प्रश्न में उदाहरण के लिए वापस आना, विधि के विनिर्देश के अनुसार एक तर्क के साथ Map<ArrayList, Something>कॉल करने के लिए मेरे लिए और मेरे लिए संभव है , और यह उस कुंजी को पुनः प्राप्त करना चाहिए जो समान सामग्रियों के साथ एक सूची है। यह संभव नहीं होगा यदि सामान्य और इसके तर्क प्रकार को प्रतिबंधित किया जाए।get()LinkedListget()


28
फिर V Get(K k)C # में क्यों है ?

134
सवाल यह है कि यदि आप फोन करना चाहते हैं, तो आपने टाइप m.get(linkedList)क्यों नहीं mकिया Map<List,Something>? मैं एक usecase के बारे में नहीं सोच सकता जहाँ एक इंटरफ़ेस पाने के m.get(HappensToBeEqual)लिए Mapटाइप बदले बिना कॉल करना समझ में आता है।
एलाजार लीबोविच

58
वाह, गंभीर डिजाइन दोष। आपको कोई कंपाइलर चेतावनी नहीं मिलती है, खराब हो जाती है। मैं एलाज़ार से सहमत हूँ। अगर यह वास्तव में उपयोगी है, जो मुझे संदेह है कि अक्सर होता है, एक getByEquals (ऑब्जेक्ट कुंजी) अधिक उचित लगता है ...
mmm

37
यह निर्णय ऐसा लगता है जैसे यह व्यावहारिकता के बजाय सैद्धांतिक शुद्धता के आधार पर किया गया था। अधिकांश उपयोगों के लिए, डेवलपर्स खासतौर पर तर्क को सीमित प्रकार से देखते हैं, जैसे कि उनके जवाब में नवागंतुक द्वारा बताए गए किनारे के मामलों का समर्थन करने के लिए असीमित है। गैर-टेम्प्लेट किए गए हस्ताक्षरों को छोड़ने से यह हल करने की तुलना में अधिक समस्याएं पैदा करता है।
सैम गोल्डबर्ग

14
@newacct: "पूरी तरह से सुरक्षित" एक निर्माण के लिए एक मजबूत दावा है जो रनटाइम में अप्रत्याशित रूप से विफल हो सकता है। अपने विचार को हैश मानचित्रों से संकुचित न करें जो उस के साथ काम करते हैं। TreeMapजब आप getविधि के लिए गलत प्रकार की वस्तुओं को पास करते हैं तो विफल हो सकते हैं , लेकिन कभी-कभी पास हो सकते हैं, उदाहरण के लिए जब नक्शा खाली होता है। और इससे भी बदतर, आपूर्ति Comparatorकी गई compareविधि के मामले में (जिसमें एक सामान्य हस्ताक्षर है!) बिना किसी अनियंत्रित चेतावनी के गलत प्रकार के तर्कों के साथ कहा जा सकता है। यह है टूटा व्यवहार।
होल्गर

105

Google पर एक भयानक जावा कोडर, केविन बॉरिलिन, ने कुछ समय पहले एक ब्लॉग पोस्ट में इस मुद्दे के बारे में लिखा था ( Setइसके बजाय संदर्भ में Map)। सबसे प्रासंगिक वाक्य:

समान रूप से, जावा कलेक्शंस फ्रेमवर्क (और गूगल कलेक्शंस लाइब्रेरी भी) के तरीके कभी भी अपने मापदंडों के प्रकारों को प्रतिबंधित नहीं करते हैं सिवाय इसके जब संग्रह को टूटने से बचाने के लिए आवश्यक हो।

मुझे पूरी तरह से यकीन नहीं है कि मैं एक सिद्धांत के रूप में इससे सहमत हूं - .NET सही कुंजी प्रकार की आवश्यकता के लिए ठीक है, उदाहरण के लिए - लेकिन यह ब्लॉग पोस्ट में तर्क का पालन करने के लायक है। (.NET का उल्लेख करने के बाद, यह समझाने का कारण है कि .NET में समस्या क्यों नहीं है इसका एक हिस्सा यह है कि अधिक सीमित संस्करण के .NET में बड़ी समस्या है ...)


4
Apocalisp: यह सच नहीं है, स्थिति अभी भी समान है।
केविन बोर्रिलियन

9
@ user102008 नहीं, पोस्ट गलत नहीं है। भले ही एक Integerऔर एक Doubleदूसरे के बराबर कभी नहीं हो सकता है, यह अभी भी एक उचित सवाल है कि क्या Set<? extends Number>इसमें मूल्य शामिल है new Integer(5)
केविन बोर्रिलिन

33
मैं कभी भी एक बार सदस्यता की जाँच नहीं करना चाहता था Set<? extends Foo>। मैंने बहुत बार नक्शे के मुख्य प्रकार को बदल दिया है और फिर निराश हो गया कि संकलक को उन सभी स्थानों पर नहीं मिल सकता है जहां कोड को अपडेट करने की आवश्यकता है। मैं वास्तव में आश्वस्त नहीं हूं कि यह सही व्यापार है।
पोर्ककुलस

4
@EarthEngine: यह हमेशा टूट गया है। यह पूरा बिंदु है - कोड टूट गया है, लेकिन संकलक इसे पकड़ नहीं सकता है।
जॉन स्कीट

1
और इसका अभी भी टूट गया है, और बस हमें एक बग ... भयानक जवाब दिया।
घोस्टकट

28

अनुबंध इस प्रकार व्यक्त किया गया है:

अधिक औपचारिक रूप से, यदि इस मानचित्र में कुंजी k से मान v तक मानचित्रण होता है जैसे कि (key == nala ; k == null: key.equals (k) ), तो यह विधि v को लौटा देती है; अन्यथा यह अशक्त हो जाता है। (इस तरह की मैपिंग में सबसे अधिक हो सकता है।)

(मेरा जोर)

और इस तरह, एक सफल कुंजी लुकअप इनपुट कुंजी के समानता पद्धति के कार्यान्वयन पर निर्भर करता है। यह आवश्यक रूप से k की श्रेणी पर निर्भर नहीं है


4
यह भी निर्भर है hashCode()। हैशकोड () के उचित कार्यान्वयन के बिना, equals()इस मामले में एक अच्छी तरह से लागू किया गया बेकार है।
रुडोल्फसन

5
मुझे लगता है, सिद्धांत रूप में, यह आपको एक कुंजी के लिए हल्के प्रॉक्सी का उपयोग करने देगा, यदि पूरी कुंजी को फिर से बनाना अव्यावहारिक था - जब तक कि बराबर () और हैशकोड () सही तरीके से लागू नहीं हो जाते।
बिल माइकेल

5
@rudolfson: जहाँ तक मुझे जानकारी है, सही बाल्टी खोजने के लिए हैश कोड पर केवल HashMap निर्भर है। उदाहरण के लिए, एक ट्रीपैप, एक बाइनरी सर्च ट्री का उपयोग करता है, और हैशकोड () के बारे में परवाह नहीं करता है।
रोब

4
कड़ाई से बोलना, संपर्क को संतुष्ट करने के लिए get()प्रकार Objectका तर्क लेने की आवश्यकता नहीं है । कल्पना कीजिए कि विधि को कुंजी प्रकार तक सीमित रखा गया है K- अनुबंध अभी भी मान्य होगा। बेशक, उपयोग करता है, जहां संकलन समय प्रकार उप-वर्ग नहीं था K, अब संकलन करने में विफल हो जाएगा, लेकिन यह अनुबंध को अमान्य नहीं करता है, क्योंकि अनुबंध से स्पष्ट रूप से चर्चा होती है कि क्या होता है यदि कोड संकलित होता है।
23

16

यह डाक कानून का एक आवेदन है , "आप जो करते हैं उसमें रूढ़िवादी रहें, जो आप दूसरों से स्वीकार करते हैं उसमें उदार हो।"

प्रकार की परवाह किए बिना समानता की जांच की जा सकती है; equalsविधि पर परिभाषित किया गया है Objectवर्ग और किसी भी स्वीकार करता है Objectएक पैरामीटर के रूप। तो, यह किसी भी को स्वीकार करने के लिए, कुंजी तुल्यता के आधार पर कुंजी तुल्यता और संचालन के लिए समझ में आता हैObject प्रकार ।

जब कोई मानचित्र मुख्य मान लौटाता है, तो यह टाइप पैरामीटर का उपयोग करके अधिक से अधिक प्रकार की जानकारी को संरक्षित करता है।


4
फिर V Get(K k)C # में क्यों है ?

1
यह V Get(K k)C # में है क्योंकि यह भी समझ में आता है। जावा और .NET के बीच अंतर वास्तव में केवल वही है जो गैर-मिलान वाले सामान को बंद कर देता है। C # में यह संकलक है, जावा में यह संग्रह है। नेट के असंगत संग्रह एक समय में एक बार कक्षाओं के बारे में मैं क्रोध, लेकिन Get()और Remove()केवल एक मिलान प्रकार निश्चित रूप से स्वीकार कर गलती में एक गलत मान गुजर करने से रोकता है।
Wormbo

26
यह पोस्टेल के कानून का गलत अनुप्रयोग है। जो आप दूसरों से स्वीकार करते हैं, उसमें उदार बनें, लेकिन बहुत उदार नहीं। इस मुहावरेदार एपीआई का मतलब है कि आप "संग्रह में नहीं" और "आपने एक स्थिर टाइपिंग गलती की" के बीच का अंतर नहीं बता सकते। कई हज़ारों प्रोग्रामर घंटों को पाने से रोक सकते थे: K -> बूलियन।
जज मेंटल

1
जरूर जो होना चाहिए था contains : K -> boolean
जज मानसिक


13

मुझे लगता है कि जेनरिक ट्यूटोरियल का यह खंड स्थिति (मेरा जोर) बताता है:

"आपको यह सुनिश्चित करने की आवश्यकता है कि जेनेरिक एपीआई पूर्ववत प्रतिबंधात्मक नहीं है; इसे एपीआई के मूल अनुबंध का समर्थन जारी रखना चाहिए। java.util.Collection के कुछ उदाहरणों पर फिर से विचार करें। पूर्व-जेनेरिक API जैसा दिखता है।"

interface Collection { 
  public boolean containsAll(Collection c);
  ...
}

इसे उत्पन्न करने का एक भोला प्रयास है:

interface Collection<E> { 
  public boolean containsAll(Collection<E> c);
  ...
}

हालांकि यह निश्चित रूप से सुरक्षित है, यह एपीआई के मूल अनुबंध तक नहीं रहता है। किसी भी तरह के आने वाले संग्रह के साथ काम करता है। यह तभी सफल होगा जब आने वाले संग्रह में वास्तव में केवल E के उदाहरण हों, लेकिन:

  • आने वाले संग्रह का स्थिर प्रकार भिन्न हो सकता है, शायद क्योंकि कॉलर को पता नहीं है कि किस प्रकार का संग्रह पास किया जा रहा है, या शायद इसलिए कि यह एक संग्रह है <S>, जहां S, E का उप-प्रकार है।
  • यह पूरी तरह से एक अलग प्रकार के संग्रह के साथAll () को कॉल करने के लिए वैध है। दिनचर्या को काम करना चाहिए, झूठे वापस आना चाहिए। ”

2
क्यों नहीं containsAll( Collection< ? extends E > c ), तो?
जज मेंटल

1
@JudgeMental, हालांकि एक उदाहरण के रूप में ऊपर यह भी अनुमति देने के लिए आवश्यक है नहीं दिया containsAllएक साथ Collection<S>जहां Sएक है महाप्रकार की E। यदि ऐसा होता तो इसकी अनुमति नहीं दी जाती containsAll( Collection< ? extends E > c )। इसके अलावा, जैसा कि स्पष्ट रूप से उदाहरण में कहा गया है, यह एक अलग प्रकार के संग्रह को पारित करने के लिए वैध है (वापसी मूल्य के साथ false)।
डेवमैक

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

6

इसका कारण यह है कि नियंत्रण द्वारा निर्धारित किया जाता है equalsऔर hashCodeकौन से तरीके हैं Objectऔर दोनों एक Objectपैरामीटर लेते हैं । यह जावा के मानक पुस्तकालयों में एक प्रारंभिक डिजाइन दोष था। जावा के प्रकार प्रणाली में सीमाओं के साथ युग्मित, यह किसी भी चीज को बाध्य करता है जो लेने के लिए बराबर और हैशकोड पर निर्भर करता है Object

जावा में टाइप-सेफ हैश टेबल और समानता रखने का एकमात्र तरीका जेनेरिक विकल्प का उपयोग करना Object.equalsऔर उसका Object.hashCodeउपयोग करना है । कार्यात्मक जावा सिर्फ इस उद्देश्य के लिए टाइप कक्षाओं के साथ आता है: Hash<A>और Equal<A>। के लिए एक आवरण HashMap<K, V>प्रदान किया जाता है जो लेता है Hash<K>और Equal<K>इसके निर्माता में। इस वर्ग की विधियाँ getऔर प्रकार का containsसामान्य तर्क लेती हैंK

उदाहरण:

HashMap<String, Integer> h =
  new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);

h.add("one", 1);

h.get("one"); // All good

h.get(Integer.valueOf(1)); // Compiler error

4
यह अपने आप में "वी '(के की) को" घोषित "होने के प्रकार को रोकता नहीं है, क्योंकि' ऑब्जेक्ट 'हमेशा K का पूर्वज होता है, इसलिए" key.hashCode () "अभी भी मान्य होगा।
फाइनेंशियल

1
हालांकि यह इसे रोकता नहीं है, मुझे लगता है कि यह इसे समझाता है। यदि उन्होंने वर्ग समानता को मजबूर करने के लिए बराबरी के तरीके को बंद कर दिया है, तो वे निश्चित रूप से लोगों को यह नहीं बता सकते हैं कि मानचित्र में ऑब्जेक्ट का पता लगाने के लिए अंतर्निहित तंत्र बराबर () और हैशमैप () का उपयोग करता है, जब उन विधियों के लिए विधि प्रोटोटाइप संगत नहीं हैं।
cgp

5

संगतता।

जेनेरिक उपलब्ध होने से पहले, बस (ऑब्जेक्ट ओ) मिलता था।

अगर उन्होंने पाने के लिए इस विधि को बदल दिया होता (<K> o) तो यह संभवत: java उपयोगकर्ताओं पर बड़े पैमाने पर कोड रखरखाव के लिए मजबूर कर देता, ताकि वर्किंग कोड को फिर से बनाया जा सके।

वे एक अतिरिक्त विधि पेश कर सकते हैं , get_checked (<K> o) कह सकते हैं और पुराने प्राप्त () विधि को हटा सकते हैं ताकि एक gentler संक्रमण पथ था। लेकिन किसी कारण से, यह नहीं किया गया था। (अब हम जो स्थिति में हैं वह यह है कि आपको मिल के प्रकार की संगतता () तर्क और मानचित्र के घोषित कुंजी प्रकार <K> के बीच जांचने के लिए findBugs जैसे टूल इंस्टॉल करने होंगे।)

मुझे लगता है कि असमानों () के शब्दार्थ से संबंधित तर्क फर्जी हैं। (तकनीकी रूप से वे सही हैं, लेकिन मुझे अभी भी लगता है कि वे फर्जी हैं। उनके दाहिने दिमाग में कोई भी डिजाइनर कभी भी o1.equals (o2) को सच नहीं करने जा रहा है यदि o1 और o2 में कोई सामान्य सुपरक्लास नहीं है।)


4

एक और अधिक वजनदार कारण है, यह तकनीकी रूप से नहीं किया जा सकता है, क्योंकि यह मैप को तोड़ता है।

जावा में बहुरूपी जेनेरिक निर्माण है <? extends SomeClass>। इस तरह के संदर्भ के साथ हस्ताक्षरित प्रकार को इंगित कर सकते हैं <AnySubclassOfSomeClass>। लेकिन बहुरूपी सामान्य बनाता है संदर्भ है कि केवल पढ़ने के लिए । कंपाइलर आपको केवल सामान्य प्रकार (जैसे साधारण गेटर्स) के रूप में सामान्य प्रकार का उपयोग करने की अनुमति देता है, लेकिन उन तरीकों का उपयोग करके ब्लॉक करता है जहां जेनेरिक प्रकार तर्क है (जैसे सामान्य सेटर)। इसका मतलब है कि अगर आप लिखते हैं Map<? extends KeyType, ValueType>, तो कंपाइलर आपको कॉल करने की अनुमति नहीं देता है get(<? extends KeyType>), और नक्शा बेकार हो जाएगा। एकमात्र तरीका इस विधि को सामान्य नहीं बनाना है get(Object):।


सेट विधि को दृढ़ता से टाइप क्यों किया जाता है?
सेंटेंज़ा

यदि आप 'पुट' का अर्थ करते हैं: पुट () विधि नक्शा बदल देती है और यह जेनेरिक के साथ उपलब्ध नहीं होगा <? SomeClass> का विस्तार करता है। यदि आप इसे कहते हैं, तो आपको संकलित अपवाद मिला। ऐसा नक्शा "आसानी से" होगा
ओवही

1

पीछे संगतता, मुझे लगता है। Map(या HashMap) अभी भी समर्थन की जरूरत है get(Object)


13
लेकिन एक ही तर्क दिया जा सकता है put(जो सामान्य प्रकारों को प्रतिबंधित करता है)। कच्चे प्रकारों का उपयोग करके आप पीछे की संगतता प्राप्त करते हैं। जेनरिक "ऑप्ट-इन" हैं।
थिलो सेप

व्यक्तिगत रूप से, मुझे लगता है कि इस डिजाइन निर्णय का सबसे संभावित कारण बैकवर्ड संगतता है।
geekdenz

1

मैं यह देख रहा था और सोच रहा था कि उन्होंने ऐसा क्यों किया। मुझे नहीं लगता कि कोई भी मौजूदा उत्तर बताता है कि वे नए जेनेरिक इंटरफ़ेस को केवल कुंजी के लिए उचित प्रकार स्वीकार क्यों नहीं कर पाए। वास्तविक कारण यह है कि भले ही उन्होंने जेनरिक की शुरुआत की, लेकिन उन्होंने एक नया इंटरफ़ेस नहीं बनाया। मैप इंटरफ़ेस समान पुराना गैर-जेनेरिक मैप है यह केवल सामान्य और गैर-जेनेरिक दोनों संस्करणों के रूप में कार्य करता है। इस तरह अगर आपके पास एक ऐसा तरीका है जो गैर-जेनेरिक मैप को स्वीकार करता है तो आप इसे पास कर सकते हैं Map<String, Customer>और यह अभी भी काम करेगा। एक ही समय में अनुबंध के लिए वस्तु स्वीकार करता है इसलिए नए इंटरफ़ेस को इस अनुबंध का भी समर्थन करना चाहिए।

मेरी राय में उन्हें एक नया इंटरफ़ेस जोड़ना चाहिए था और मौजूदा संग्रह पर दोनों को लागू करना चाहिए था, लेकिन उन्होंने संगत इंटरफेस के पक्ष में फैसला किया, भले ही इसका मतलब है कि विधि प्राप्त करने के लिए बदतर डिज़ाइन। ध्यान दें कि संग्रह स्वयं मौजूदा तरीकों के साथ संगत होंगे केवल इंटरफेस नहीं होंगे।


0

हम अभी-अभी बड़ी रीफैक्टरिंग कर रहे हैं और हम यह जांचने के लिए दृढ़ता से टाइप्ड गेट () को याद कर रहे थे कि हम पुराने टाइप के साथ कुछ पाने () से चूक तो नहीं गए।

लेकिन मुझे संकलन समय की जांच के लिए वर्कअराउंड / बदसूरत ट्रिक मिला: मैप इंटरफेस बनाएं जिसमें दृढ़ता से टाइप किया गया हो, इसमें शामिल हैं, निकालें ... और इसे अपने प्रोजेक्ट के java.util पैकेज में डालें।

आपको कॉलिंग गेट (), ... गलत प्रकारों के लिए संकलन त्रुटियां मिलेंगी, दूसरों को कंपाइलर के लिए सब कुछ ठीक लगता है (कम से कम एक्लिप्स केलर के अंदर)।

अपने निर्माण की जांच के बाद इस इंटरफ़ेस को हटाना न भूलें क्योंकि यह वह नहीं है जो आप रनटाइम में चाहते हैं।

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