क्या स्ट्रीम तत्व को संशोधित करने के लिए तिरछा () का उपयोग करना एक एंटीपैटर्न है?


36

मान लीजिए कि मेरे पास चीजों की एक धारा है और मैं उन्हें मध्य धारा में "समृद्ध" करना चाहता हूं, मैं यह करने के peek()लिए उपयोग कर सकता हूं , जैसे:

streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

मान लें कि कोड में इस बिंदु पर चीजों को बदलना सही व्यवहार है - उदाहरण के लिए, thingMutatorविधि "lastProcessed" फ़ील्ड को वर्तमान समय में सेट कर सकती है।

हालाँकि, peek()अधिकांश संदर्भों का अर्थ है "देखो, लेकिन स्पर्श मत करो"।

धारा तत्वों का एक उत्परिवर्ती या गलत सलाह peek()देने के लिए उपयोग कर रहा है ?

संपादित करें:

उपभोक्ता को बदलने के लिए वैकल्पिक, अधिक पारंपरिक, दृष्टिकोण होगा:

private void thingMutator(Thing thing) {
    thing.setLastProcessed(System.currentTimeMillis());
}

एक फ़ंक्शन जो पैरामीटर लौटाता है:

private Thing thingMutator(Thing thing) {
    thing.setLastProcessed(currentTimeMillis());
    return thing;
}

और map()इसके बजाय उपयोग करें :

stream.map(this::thingMutator)...

लेकिन वह परफ़ेक्ट्री कोड ( return) का परिचय देता है और मुझे यकीन नहीं है कि यह स्पष्ट है, क्योंकि आप जानते हैं कि आप peek()एक ही वस्तु लौटाते हैं, लेकिन map()एक नज़र में यह भी स्पष्ट नहीं है कि यह वस्तु का एक ही वर्ग है।

इसके अलावा, peek()आपके पास एक लैम्ब्डा हो सकता है जो उत्परिवर्तित करता है, लेकिन आपके साथ map()एक ट्रेन मलबे का निर्माण करना होगा। की तुलना करें:

stream.peek(t -> t.setLastProcessed(currentTimeMillis())).forEach(...)
stream.map(t -> {t.setLastProcessed(currentTimeMillis()); return t;}).forEach(...)

मुझे लगता है कि peek()संस्करण स्पष्ट है, और लैम्ब्डा स्पष्ट रूप से उत्परिवर्तन कर रहा है, इसलिए कोई "रहस्यमय" साइड इफेक्ट नहीं है। इसी तरह, यदि एक विधि संदर्भ का उपयोग किया जाता है और विधि का नाम स्पष्ट रूप से उत्परिवर्तन उत्परिवर्तित होता है, तो वह भी स्पष्ट और स्पष्ट है।

एक व्यक्तिगत नोट पर, मैं peek()म्यूट करने के लिए उपयोग करने से नहीं शर्माता - मुझे यह बहुत सुविधाजनक लगता है।



1
क्या आपने peekएक धारा के साथ प्रयोग किया है जो अपने तत्वों को गतिशील रूप से उत्पन्न करता है? क्या यह अभी भी काम करता है, या परिवर्तन खो गए हैं? एक धारा के तत्वों को संशोधित करना मेरे लिए अविश्वसनीय लगता है।
सेबस्टियन रेडल

@SebastianRedl मैंने इसे 100% विश्वसनीय पाया है। मैंने पहली बार इसका उपयोग प्रसंस्करण के दौरान इकट्ठा करने के लिए किया था: List<Thing> list; things.stream().peek(list::add).forEach(...);बहुत ही आसान। हाल ही में। मैंने प्रकाशन के लिए जानकारी जोड़ने के लिए इसका उपयोग किया है Map<Thing, Long> timestamps = ...; return things.stream().peek(t -> t.setTimestamp(timestamp.get(t))).collect(toList());:। मुझे पता है कि इस उदाहरण को करने के अन्य तरीके हैं, लेकिन मैं यहां बहुत अधिक सरल हूं। peek()अधिक कॉम्पैक्ट, और अधिक सुरुचिपूर्ण कोड IMHO का उपयोग करके । पठनीयता एक तरफ, यह सवाल वास्तव में है कि आपने क्या उठाया है; क्या यह सुरक्षित / विश्वसनीय है?
बोहेमियन


@Bohemian। क्या आप अभी भी इस दृष्टिकोण के साथ अभ्यास कर रहे हैं peek? स्टैकओवरफ्लो पर मेरा भी यही सवाल है और उम्मीद है कि आप इसे देख सकते हैं और अपनी प्रतिक्रिया दे सकते हैं। धन्यवाद। stackoverflow.com/questions/47356992/…
tsolakp

जवाबों:


23

आप सही हैं, शब्द के अंग्रेजी अर्थ में "झांकना" का अर्थ है "देखो, लेकिन स्पर्श न करें।"

हालांकि JavaDoc कहता है:

झांकना

स्ट्रीम झांकना (उपभोक्ता कार्रवाई)

इस धारा के तत्वों से मिलकर एक धारा लौटाता है, इसके अतिरिक्त प्रत्येक तत्व पर प्रदत्त क्रिया करता है क्योंकि परिणामी धारा से तत्वों की खपत होती है।

मुख्य शब्द: "प्रदर्शन ... कार्रवाई" और "भस्म"। JavaDoc बहुत स्पष्ट है कि हमें peekधारा को संशोधित करने की क्षमता होने की उम्मीद करनी चाहिए ।

हालाँकि JavaDoc भी बताता है:

एपीआई नोट:

यह विधि मुख्य रूप से डिबगिंग का समर्थन करने के लिए मौजूद है, जहां आप तत्वों को देखना चाहते हैं क्योंकि वे एक निश्चित बिंदु पर एक पाइपलाइन में प्रवाह करते हैं

यह इंगित करता है कि यह अवलोकन करने के लिए अधिक है, उदाहरण के लिए धारा में लॉगिंग तत्व।

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

बहुत कम से कम, मैं इन पंक्तियों के साथ आपके कोड में एक संक्षिप्त टिप्पणी जोड़ूंगा:

// Note: this peek() call will modify the Things in the stream.
streamOfThings.peek(this::thingMutator).forEach(this::someConsumer);

इस तरह की टिप्पणियों की उपयोगिता पर राय अलग-अलग है, लेकिन मैं इस मामले में इस तरह की टिप्पणी का उपयोग करूंगा।


1
यदि आपको विधि नाम स्पष्ट रूप से एक उत्परिवर्तन, उदा thingMutator, या अधिक ठोस resetLastProcessedआदि का संकेत देता है, तो जब तक कोई सम्मोहक कारण न हो, तब आपको टिप्पणी की आवश्यकता होती है, जैसे कि आपके सुझाव जैसे सुझाव आमतौर पर चर और / या विधि नामों के खराब विकल्प दर्शाते हैं। यदि अच्छे नाम चुने जाते हैं, तो क्या यह पर्याप्त नहीं है? या आप यह कह रहे हैं कि एक अच्छे नाम के बावजूद, कुछ भी peek()एक अंधे स्थान की तरह है जो अधिकांश प्रोग्रामर "स्किम" (नेत्रहीन स्किप) करेंगे? इसके अलावा, "डिबगिंग के लिए मुख्य रूप से" केवल "डीबगिंग के लिए" के रूप में एक ही बात नहीं है - "मुख्य रूप से" वाले लोगों के अलावा अन्य क्या उद्देश्य थे?
बोहेमियन

@ बोहेमियन मैं इसे मुख्य रूप से समझाता हूं क्योंकि विधि का नाम गैर-सहज है। और मैं ओरेकल के लिए काम नहीं करता। मैं उनकी जावा टीम में नहीं हूं। मुझे नहीं पता कि उनका क्या इरादा था।

@Bohemian क्योंकि जब मैं जिस तरह से तत्वों को संशोधित नहीं करता है उसे ढूंढता हूं तो मुझे "झांकना" पर लाइन पढ़ना बंद हो जाएगा क्योंकि "झांकना" कुछ भी नहीं बदलता है। केवल बाद में जब कोड के अन्य सभी स्थानों में बग नहीं था जिसका मैं पीछा कर रहा हूं तो क्या मैं इसके साथ वापस आऊंगा, संभवतः एक डिबगर के साथ सशस्त्र हो सकता है और शायद फिर "omg, जो इस भ्रामक कोड को वहां रखता है ?!"
फ्रैंक हॉपकिन्स

@ इस परीक्षा में बोहेमियन है जहाँ सब कुछ एक लाइन में है, किसी को वैसे भी पढ़ने की ज़रूरत है, लेकिन एक व्यक्ति "झांकना" की सामग्री को छोड़ सकता है और अक्सर एक स्ट्रीम स्टेटमेंट प्रति लाइन एक स्ट्रीम ऑपरेशन के साथ कई लाइनों पर जाता है
फ्रैंक हॉपकिंस

1

इसका आसानी से गलत अर्थ निकाला जा सकता है, इसलिए मैं इसे इस तरह इस्तेमाल करने से बचूंगा। संभवतः सबसे अच्छा विकल्प दो कार्यों को मर्ज करने के लिए एक लंबो फ़ंक्शन का उपयोग करना है जो आपको कॉल करने की आवश्यकता है। आप मौजूदा को बदलने के बजाय एक नई वस्तु को वापस करने पर विचार कर सकते हैं - यह थोड़ा कम कुशल हो सकता है, लेकिन इसके परिणामस्वरूप अधिक पठनीय कोड होने की संभावना है, और किसी अन्य ऑपरेशन के लिए संशोधित सूची का उपयोग करके गलती से संभावित नुकसान को कम करना चाहिए मूल प्राप्त किया है।


-2

एपीआई नोट हमें बताता है कि विधि को मुख्य रूप से डीबगिंग / लॉगिंग / फायरिंग आँकड़े आदि जैसे कार्यों के लिए जोड़ा गया है।

@apiNote यह विधि मुख्य रूप से डिबगिंग का समर्थन करने के लिए मौजूद है, जहाँ आप चाहते हैं कि तत्वों को देखने के लिए जैसे कि वे एक पाइपलाइन में एक निश्चित बिंदु पर प्रवाहित हों: *

       {@code
     * Stream.of ("एक", "दो", "तीन", "चार")
     * .फिल्टर (e -> e.length ()> 3)
     * .peek (e -> System.out.println ("फ़िल्टर किया गया मान:" + e))
     * .मैप (स्ट्रिंग :: toUpperCase)
     * .peek (e -> System.out.println ("मैप किया गया मान:" + e))
     * .collect (कलेक्टर .toList ());
     * *


2
आपका जवाब स्नोमैन द्वारा पहले से ही कवर किया गया है।
विंसेंट सवार्द

3
और "मुख्य रूप से" का अर्थ "केवल" नहीं है।
बोहेमियन

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