क्या किसी ImmutableMap या मैप को वापस करना बेहतर है?


90

मान लीजिए कि मैं एक ऐसी विधि लिख रहा हूं, जिसमें एक नक्शा वापस आ जाना चाहिए । उदाहरण के लिए:

public Map<String, Integer> foo() {
  return new HashMap<String, Integer>();
}

कुछ समय के लिए इसके बारे में सोचने के बाद, मैंने फैसला किया है कि इस मैप को बनाते समय इसे संशोधित करने का कोई कारण नहीं है। इस प्रकार, मैं एक ImmutableMap वापस करना चाहूंगा ।

public Map<String, Integer> foo() {
  return ImmutableMap.of();
}

क्या मुझे रिटर्न टाइप को जेनेरिक मैप के रूप में छोड़ना चाहिए, या क्या मुझे यह निर्दिष्ट करना चाहिए कि मैं एक इम्यूटेबल मैप लौटा रहा हूं?

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


2
मुझे लगता है कि कोई व्यक्ति अंततः इस सवाल को टैग करेगा क्योंकि यह तर्क के लिए भी खुला है। क्या आप "WDYT?" के बजाय अधिक विशिष्ट प्रश्नों में लगा सकते हैं? और "क्या यह ठीक है ...?" उदाहरण के लिए, "क्या कोई समस्या या विचार हैं जो एक नियमित मानचित्र पर लौट रहे हैं?" और "क्या विकल्प मौजूद हैं?"
राख

क्या होगा अगर आप बाद में तय करते हैं कि यह वास्तव में एक परिवर्तनशील मानचित्र वापस करना चाहिए?
user253751

3
@ मिनीबिस लोल, या उस मामले के लिए एक सूची?
djechlin

3
क्या आप वास्तव में अपने एपीआई में अमरूद की निर्भरता सेंकना चाहते हैं? पिछड़ी अनुकूलता को तोड़े बिना आप इसे बदल नहीं पाएंगे।
user2357112

1
मुझे लगता है कि यह प्रश्न बहुत अधिक अस्पष्ट है, और कई महत्वपूर्ण विवरण गायब हैं। उदाहरण के लिए, क्या आपके पास पहले से ही एक (दृश्यमान) निर्भरता के रूप में अमरूद है। यहां तक ​​कि अगर आपके पास पहले से ही है, तो कोड स्निपेट पीसुडोकोड हैं, और यह मत बताइए कि आप वास्तव में क्या हासिल करना चाहते हैं। आप निश्चित रूप से नहीं लिखेंगे return ImmutableMap.of(somethingElse)। इसके बजाय, आप ImmutableMap.of(somethingElse)एक फ़ील्ड के रूप में संग्रहीत करेंगे, और केवल इस फ़ील्ड को वापस करेंगे। यह सब यहां के डिजाइन को प्रभावित करता है।
मार्को 13

जवाबों:


70
  • यदि आप एक सार्वजनिक-सामना कर रहे एपीआई लिख रहे हैं और यह अपरिवर्तनीयता आपके डिजाइन का एक महत्वपूर्ण पहलू है, तो मैं निश्चित रूप से या तो यह स्पष्ट कर दूंगा कि विधि का नाम स्पष्ट रूप से दर्शाया गया है कि लौटा हुआ नक्शा अपरिवर्तनीय होगा या कंक्रीट के प्रकार को वापस करके नक्शा। इसे जावदोक में उल्लेखित करना मेरे विचार में पर्याप्त नहीं है।

    चूंकि आप स्पष्ट रूप से अमरूद कार्यान्वयन का उपयोग कर रहे हैं, मैंने डॉक को देखा और यह एक सार वर्ग है इसलिए यह आपको वास्तविक, ठोस प्रकार पर थोड़ा लचीलापन देता है।

  • यदि आप एक आंतरिक उपकरण / पुस्तकालय लिख रहे हैं, तो यह सिर्फ एक सादे वापस करने के लिए अधिक स्वीकार्य हो जाता है Map। लोग कोड के आंतरिक भाग के बारे में जानेंगे जिसे वे बुला रहे हैं या कम से कम इसके लिए आसान पहुंच होगी।

मेरा निष्कर्ष यह होगा कि स्पष्ट अच्छा है, चीजों को मौका न छोड़ें।


1
एक अतिरिक्त इंटरफ़ेस, एक अतिरिक्त अमूर्त वर्ग, और एक वर्ग जो इस सार वर्ग का विस्तार करता है। यह इस तरह के एक साधारण बात के लिए बहुत अधिक परेशानी है क्योंकि ओपी पूछ रहा है, जो कि यदि विधि Mapइंटरफ़ेस वापसी या ImmutableMapकार्यान्वयन प्रकार के लिए जोर देती है ।
एफपीएस

20
यदि आप अमरूद की बात कर रहे हैं ImmutableMap, तो अमरूद की टीम की सलाह है ImmutableMapकि वे शब्दार्थ गारंटी के साथ एक इंटरफेस पर विचार करें, और आपको उस प्रकार को सीधे वापस करना चाहिए।
लुई वासरमैन

1
@LouisWasserman mm, यह नहीं देख पाया कि यह प्रश्न अमरूद कार्यान्वयन के लिए एक लिंक दे रहा था। मैं तब स्निपेट
निकालूंगा

3
मैं लुईस से सहमत हूं, यदि आपका इरादा एक ऐसा नक्शा वापस करना है जो एक अपरिवर्तनीय वस्तु है, तो यह सुनिश्चित करना है कि वापसी वस्तु उस प्रकार की हो। यदि किसी कारण से आप इस तथ्य को अस्पष्ट करना चाहते हैं कि यह एक अपरिवर्तनीय प्रकार है, तो अपने स्वयं के इंटरफ़ेस को परिभाषित करें। इसे मानचित्र के रूप में छोड़ना भ्रामक है।
WSimpson

1
@WSimpson मुझे विश्वास है कि मेरा जवाब क्या कह रहा है। लुई कुछ स्निपेट के बारे में बात कर रहा था जिसे मैंने जोड़ा था, लेकिन मैंने इसे हटा दिया।
डिसी

38

आपको ImmutableMapअपने रिटर्न प्रकार के रूप में होना चाहिए । Mapऐसे तरीके शामिल हैं जो ImmutableMap(जैसे put) के कार्यान्वयन से समर्थित नहीं हैं और में चिह्नित @deprecatedहैं ImmutableMap

पदावनत विधियों का उपयोग करने पर एक संकलक चेतावनी होगी और अधिकांश आईडीई चेतावनी देगा जब लोग पदावनत विधियों का उपयोग करने का प्रयास करेंगे।

यह उन्नत चेतावनी आपके पहले संकेत के रूप में रनटाइम अपवाद रखने के लिए बेहतर है कि कुछ गलत है।


1
यह मेरी राय में सबसे समझदार उत्तर है। मैप इंटरफ़ेस एक अनुबंध है, और इम्यूटेबल मैप स्पष्ट रूप से इसके एक महत्वपूर्ण हिस्से का उल्लंघन कर रहा है। इस मामले में मानचित्र को रिटर्न प्रकार के रूप में उपयोग करने का कोई मतलब नहीं है।
टोनियोइलग्रिंगो

@TonioElGringo Collections.immutableMapतब के बारे में क्या ?
ऑरेंजडॉग

@TonioElGringo कृपया ध्यान दें कि ImmutableMap लागू नहीं करने वाले तरीकों को इंटरफ़ेस के दस्तावेज़ docs.oracle.com/javase/7/docs/api/java/util/… में स्पष्ट रूप से वैकल्पिक के रूप में चिह्नित किया गया है । इस प्रकार, मुझे लगता है कि यह अभी भी एक नक्शा (उचित javadocs के साथ) वापस करने के लिए समझ में आ सकता है। हालांकि, मैं ऊपर वर्णित कारणों की वजह से, स्पष्ट रूप से ImmutableMap को वापस करने का चयन करता हूं।
AMT

3
@TonioElGringo यह कुछ भी नहीं तोड़ रहा है, वे तरीके वैकल्पिक हैं। जैसे List, इसमें कोई गारंटी नहीं है कि List::addवैध है। कोशिश करो Arrays.asList("a", "b").add("c");। कोई संकेत नहीं है कि यह रनटाइम में विफल हो जाएगा, फिर भी यह करता है। कम से कम अमरूद आपको एक सिर देता है।
njzk2

14

दूसरी ओर, अगर मैं इसे इस तरह छोड़ दूंगा, तो अन्य डेवलपर्स इस तथ्य को याद कर सकते हैं कि यह ऑब्जेक्ट अपरिवर्तनीय है।

आपको यह उल्लेख करना चाहिए कि javadocs में। डेवलपर्स उन्हें पढ़ते हैं, आप जानते हैं।

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

कोई भी डेवलपर अपने कोड को अप्रकाशित नहीं करता है। और जब वह इसका परीक्षण करता है, तो उसे एक अपवाद मिलता है जहां वह न केवल कारण देखता है, बल्कि उस फ़ाइल और लाइन को भी देखता है जहां उसने एक अपरिवर्तनीय मानचित्र पर लिखने की कोशिश की थी।

हालांकि ध्यान दें, केवल वही Mapअपरिवर्तनीय होगा, न कि उसमें मौजूद वस्तुएं।


10
"कोई भी डेवलपर अपने कोड को प्रकाशित नहीं करता है।" क्या आप इस पर दांव लगाना चाहते हैं? सार्वजनिक सॉफ़्टवेयर में बहुत कम (मेरे अनुमान) बग होंगे, अगर यह वाक्य सही होगा। मैं "अधिकांश डेवलपर्स ..." के बारे में भी निश्चित नहीं होगा। और हाँ, यह निराशाजनक है।
टॉम

1
ठीक है, टॉम सही है, मुझे यकीन नहीं है कि आप क्या कहना चाह रहे हैं, लेकिन आपने जो लिखा है वह स्पष्ट रूप से गलत है। (इसके अलावा लिंग समावेश के लिए कुहनी
मारना

@ मुझे लगता है कि आप इस उत्तर में व्यंग्य करने से चूक गए हैं
कप्तान फोगेटी

10

अगर मैं इसे इस तरह छोड़ दूंगा, तो अन्य डेवलपर्स इस तथ्य को याद कर सकते हैं कि यह ऑब्जेक्ट अपरिवर्तनीय है

यह सच है, लेकिन अन्य डेवलपर्स को अपने कोड का परीक्षण करना चाहिए और यह सुनिश्चित करना चाहिए कि यह कवर किया गया है।

फिर भी आपके पास इसे हल करने के लिए 2 और विकल्प हैं:

  • जावदोक का उपयोग करें

    @return a immutable map
  • वर्णनात्मक विधि नाम का चयन करें

    public Map<String, Integer> getImmutableMap()
    public Map<String, Integer> getUnmodifiableEntries()
    

    एक ठोस उपयोग के मामले के लिए आप बेहतर तरीकों का नाम भी दे सकते हैं। उदाहरण के लिए

    public Map<String, Integer> getUnmodifiableCountByWords()

आप और क्या कर सकते हैं?!

आप वापसी कर सकते हैं

  • प्रतिलिपि

    private Map<String, Integer> myMap;
    
    public Map<String, Integer> foo() {
      return new HashMap<String, Integer>(myMap);
    }
    

    इस दृष्टिकोण का उपयोग किया जाना चाहिए यदि आप उम्मीद करते हैं कि बहुत सारे ग्राहक मानचित्र को संशोधित करेंगे और जब तक कि मानचित्र में केवल कुछ प्रविष्टियां न हों।

  • CopyOnWriteMap

    लिखने के संग्रह पर प्रतिलिपि आमतौर पर उपयोग की जाती है जब आपको
    संगामिति से निपटना पड़ता है। लेकिन अवधारणा आपकी स्थिति में भी आपकी मदद करेगी, क्योंकि CopyOnWriteMap एक म्यूटेटिव ऑपरेशन (उदाहरण के लिए, हटाएं) पर आंतरिक डेटा संरचना की एक प्रति बनाता है।

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

    इस दृष्टिकोण का उपयोग किया जाना चाहिए यदि आप उम्मीद करते हैं कि कुछ ग्राहक मानचित्र को संशोधित करेंगे।

    दुख की बात है कि जावा में ऐसा नहीं है CopyOnWriteMap। लेकिन हो सकता है कि आप किसी तीसरे पक्ष को खोज लें या उसे स्वयं लागू कर दें।

अंत में आपको यह ध्यान रखना चाहिए कि आपके नक्शे में तत्व अभी भी परिवर्तनशील हो सकते हैं।


8

निश्चित रूप से एक ImmutableMap लौटाएँ, औचित्य:

  • विधि हस्ताक्षर (रिटर्न प्रकार सहित) स्व-दस्तावेजीकरण होना चाहिए। टिप्पणियाँ ग्राहक सेवा की तरह हैं: यदि आपके ग्राहकों को उन पर भरोसा करने की आवश्यकता है, तो आपका प्राथमिक उत्पाद दोषपूर्ण है।
  • क्या कुछ एक इंटरफ़ेस है या एक वर्ग केवल प्रासंगिक है जब इसे विस्तारित या कार्यान्वित किया जाता है। एक उदाहरण (ऑब्जेक्ट) को देखते हुए, 99% समय क्लाइंट कोड को पता नहीं चलेगा या परवाह नहीं करेगा कि क्या कोई इंटरफ़ेस या एक क्लास है। मैंने पहली बार में यह मान लिया था कि ImmutableMap एक इंटरफ़ेस था। लिंक पर क्लिक करने के बाद ही मुझे एहसास हुआ कि यह एक वर्ग है।

5

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

public ImmutableMap<String, Integer> foo() {
    return ImmutableMap.copyOf(internalMap);
}

अमरूद हर बार नक्शे की नकल करेगा। वह धीमा है। लेकिन अगर internalMapपहले से ही एक था ImmutableMap, तो यह पूरी तरह से ठीक है।

यदि आप अपनी कक्षा को लौटने के लिए प्रतिबंधित नहीं करते हैं ImmutableMap, तो आप इसके बदले वापसी कर सकते हैं Collections.unmodifiableMap:

public Map<String, Integer> foo() {
    return Collections.unmodifiableMap(internalMap);
}

ध्यान दें कि यह मानचित्र में एक अपरिवर्तनीय दृश्य है । यदि internalMapपरिवर्तन होता है, तो इसकी एक कैश्ड प्रति होगी Collections.unmodifiableMap(internalMap)। मैं अभी भी इसे पाने वालों के लिए पसंद करता हूं।


1
ये अच्छे बिंदु हैं, लेकिन पूर्णता के लिए, मुझे यह उत्तर पसंद है जो बताता है कि unmodifiableMapसंदर्भ के धारक द्वारा केवल अप्रमाणिक है - यह एक दृश्य है और बैकिंग मैप के धारक बैकिंग मैप को संशोधित कर सकता है और फिर दृश्य बदल जाता है। तो यह एक महत्वपूर्ण विचार है।
दाविदबक

@ के रूप में davidback टिप्पणी की, का उपयोग करने के बारे में बहुत सावधान रहना होगा Collections.unmodifiableMap। यह विधि एक अलग विशिष्ट नए मानचित्र ऑब्जेक्ट बनाने के बजाय मूल मानचित्र पर एक दृश्य लौटाती है । इसलिए मूल मानचित्र को संदर्भित करने वाला कोई भी अन्य कोड इसे संशोधित कर सकता है, और फिर माना जाता है कि असम्पीडित मानचित्र का धारक हार्ड-वे सीखता है कि मानचित्र वास्तव में उनके नीचे से संशोधित किया जा सकता है। मैं खुद इस तथ्य से थोड़ा संभल गया हूं। मैंने मानचित्र की एक प्रति लौटना या अमरूद का उपयोग करना सीखा ImmutableMap
बेसिल बॉर्क

5

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

public Integer fooOf(String key) {
    return map.get(key);
}

यह एपीआई को बहुत तंग करता है। यदि मानचित्र वास्तव में आवश्यक है, तो यह प्रविष्टियों की एक धारा प्रदान करके एपीआई के ग्राहक के लिए छोड़ा जा सकता है:

public Stream<Map.Entry<String, Integer>> foos() {
    map.entrySet().stream()
}

फिर ग्राहक अपनी जरूरत के अनुसार अपना अपरिवर्तनीय या परिवर्तनशील मानचित्र बना सकता है या प्रविष्टियों को अपने मानचित्र में जोड़ सकता है। यदि ग्राहक को यह जानना होगा कि क्या मूल्य मौजूद है, तो इसके बजाय वैकल्पिक लौटाया जा सकता है:

public Optional<Integer> fooOf(String key) {
    return Optional.ofNullable(map.get(key));
}

-1

अपरिवर्तनीय मानचित्र एक प्रकार का मानचित्र है। इसलिए मैप का रिटर्न प्रकार छोड़ना ठीक है।

यह सुनिश्चित करने के लिए कि उपयोगकर्ता लौटी हुई वस्तु को संशोधित नहीं करते हैं, विधि का प्रलेखन लौटी हुई वस्तु की विशेषताओं का वर्णन कर सकता है।


-1

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

निम्नलिखित लेख पर एक नज़र डालें:

एंडी गिब्सन


1
अनावश्यक आवरणों को हानिकारक माना जाता है।
djechlin

आप सही हैं, मैं यहां एक रैपर का उपयोग नहीं करूंगा, क्योंकि इंटरफ़ेस पर्याप्त है।
WSimpson

मुझे लगता है कि अगर यह करने के लिए सही बात थी, तो ImmutableMap पहले से ही एक ImmutableMapInterface को लागू करेगा, लेकिन मुझे यह तकनीकी रूप से समझ में नहीं आता है।
djechlin

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

Mapइंटरफ़ेस को विस्तारित (या कार्यान्वित) नहीं करने वाले किसी चीज़ को वापस करने का नुकसान यह है कि आप इसे Mapबिना किसी एपीपी फ़ंक्शन के पास नहीं कर सकते हैं, जो इसे कास्टिंग के बिना उम्मीद करता है। इसमें अन्य प्रकार के मानचित्रों के सभी प्रकार के प्रतिलिपि-निर्माता शामिल हैं। इसके अलावा, अगर इंटरफ़ेस द्वारा परिवर्तनशीलता केवल "छिपी" है, तो आपके उपयोगकर्ता हमेशा इस तरह से कास्टिंग करके अपना कोड तोड़ सकते हैं।
हल्क
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.