जावा स्ट्रीम में वास्तव में केवल डीबगिंग के लिए है?


137

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

क्या होगा यदि मेरे पास एक स्ट्रीम है जहां प्रत्येक खाते में एक उपयोगकर्ता नाम, पासवर्ड फ़ील्ड और एक लॉगिन () और लॉग इन () विधि है।

मेरे पास भी है

Consumer<Account> login = account -> account.login();

तथा

Predicate<Account> loggedIn = account -> account.loggedIn();

यह इतना बुरा क्यों होगा?

List<Account> accounts; //assume it's been setup
List<Account> loggedInAccount = 
accounts.stream()
    .peek(login)
    .filter(loggedIn)
    .collect(Collectors.toList());

अब जहां तक ​​मैं बता सकता हूं कि यह वही है जो यह करने का इरादा है। यह;

  • खातों की एक सूची लेता है
  • प्रत्येक खाते में प्रवेश करने की कोशिश करता है
  • ऐसे किसी भी खाते को फ़िल्टर करता है जो लॉग इन नहीं है
  • लॉग इन खातों को एक नई सूची में एकत्रित करता है

ऐसा कुछ करने का नकारात्मक पक्ष क्या है? किसी भी कारण से मुझे आगे नहीं बढ़ना चाहिए? अन्त में, यदि यह समाधान नहीं है तो क्या है?

इस के मूल संस्करण ने .filter () विधि का उपयोग इस प्रकार किया;

.filter(account -> {
        account.login();
        return account.loggedIn();
    })

38
जब भी मुझे खुद को एक मल्टी-लाइन लैम्बा की आवश्यकता होती है, मैं लाइनों को एक निजी विधि में स्थानांतरित करता हूं और लैम्बडा के बजाय विधि संदर्भ पास करता हूं।
VGR

1
क्या इरादा है - क्या आप सभी खातों को लॉग इन करने की कोशिश कर रहे हैं और यदि वे लॉग इन (जो तुच्छ रूप से सच हो सकते हैं) के आधार पर उन्हें फ़िल्टर कर सकते हैं? या, क्या आप उन्हें लॉग इन करना चाहते हैं, फिर उन्हें लॉग इन करें या नहीं , इसके आधार पर फ़िल्टर करें? मैं इसे इस क्रम में पूछ रहा हूं क्योंकि forEachहो सकता है कि आप जिस ऑपरेशन का विरोध करना चाहते हैं peek। सिर्फ इसलिए कि एपीआई में इसका मतलब यह नहीं है कि यह दुरुपयोग (जैसे Optional.of) के लिए खुला नहीं है ।
मकोतो

8
यह भी ध्यान दें कि आपका कोड बस हो सकता है .peek(Account::login)और .filter(Account::loggedIn); उपभोक्ता और विधेय लिखने का कोई कारण नहीं है कि जैसे कोई दूसरी विधि कहता है।
जोशुआ टेलर 21

2
यह भी ध्यान दें कि धारा एपीआई स्पष्ट रूप से व्यवहार मापदंडों में दुष्प्रभावों को हतोत्साहित करता है
डिडिएर एल

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

जवाबों:


77

इस से प्रमुख takeaway:

अनपेक्षित तरीके से एपीआई का उपयोग न करें, भले ही यह आपके तात्कालिक लक्ष्य को पूरा करता हो। यह दृष्टिकोण भविष्य में टूट सकता है, और यह भविष्य के रखरखाव के लिए भी स्पष्ट नहीं है।


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

forEachइस ऑपरेशन का उपयोग करने से यह अनुरक्षक को स्पष्ट हो जाएगा कि प्रत्येक तत्व पर एक इरादा दुष्प्रभाव है accounts, और आप कुछ ऑपरेशन कर रहे हैं जो इसे बदल सकते हैं।

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

accounts.forEach(a -> a.login());
List<Account> loggedInAccounts = accounts.stream()
                                         .filter(Account::loggedIn)
                                         .collect(Collectors.toList());

3
यदि आप एक प्रीप्रोसेसिंग चरण में लॉगिन करते हैं, तो आपको एक स्ट्रीम की आवश्यकता नहीं है। आप forEachस्रोत संग्रह में सही प्रदर्शन कर सकते हैं :accounts.forEach(a -> a.login());
Holger

1
@ होलगर: उत्कृष्ट बिंदु। मैंने उसे उत्तर में शामिल कर लिया है।
Makoto

2
@ एडम.जे: ठीक है, मेरे जवाब में आपके शीर्षक में निहित सामान्य प्रश्न पर अधिक ध्यान केंद्रित किया गया है, अर्थात यह विधि वास्तव में केवल डीबगिंग के लिए है, उस पद्धति के पहलुओं को समझाकर। यह उत्तर आपके वास्तविक उपयोग के मामले में अधिक फ़्यूज़ है और इसके बजाय इसे कैसे करना है। तो आप कह सकते हैं, एक साथ वे पूरी तस्वीर प्रदान करते हैं। पहला, इसका कारण यह है कि इसका उपयोग नहीं किया गया है, दूसरा निष्कर्ष, एक अनपेक्षित उपयोग के लिए न रहना और इसके बजाय क्या करना है। बाद का आपके लिए अधिक व्यावहारिक उपयोग होगा।
होल्गर

2
बेशक, यह बहुत आसान था अगर login()विधि ने booleanसफलता की स्थिति का संकेत देने वाला एक मूल्य लौटा दिया ...
होल्गर

3
यही मेरा लक्ष्य था। यदि कोई login()रिटर्न देता है boolean, तो आप इसे एक विधेय के रूप में उपयोग कर सकते हैं जो सबसे साफ समाधान है। इसका अभी भी एक साइड इफेक्ट है, लेकिन यह तब तक ठीक है जब तक कि यह नॉन- loginइंटरफेरिंग है , अर्थात किसी Accountकी प्रक्रिया `का दूसरे के लॉगिन` प्रक्रिया` पर कोई प्रभाव नहीं पड़ता है Account
होल्गर

111

आपको जो महत्वपूर्ण बात समझनी है, वह यह है कि धाराएँ टर्मिनल ऑपरेशन द्वारा संचालित होती हैं । टर्मिनल ऑपरेशन यह निर्धारित करता है कि सभी तत्वों को संसाधित किया जाना है या बिल्कुल भी। तो collectएक ऐसा ऑपरेशन है जो प्रत्येक आइटम को संसाधित करता है, जबकि findAnyएक मिलान तत्व का सामना करने के बाद आइटम को संसाधित करना रोक सकता है।

और count()किसी भी तत्व को संसाधित नहीं कर सकता है जब यह आइटम को संसाधित किए बिना स्ट्रीम के आकार को निर्धारित कर सकता है। चूंकि यह जावा 8 में बनाया गया अनुकूलन नहीं है, लेकिन जो जावा 9 में होगा, जब आप जावा 9 पर स्विच करते हैं और count()सभी आइटमों को संसाधित करने पर भरोसा करने वाले कोड हो सकते हैं। यह अन्य कार्यान्वयन-निर्भर विवरणों से भी जुड़ा हुआ है, उदाहरण के लिए जावा 9 में भी, संदर्भ कार्यान्वयन एक अनंत धारा स्रोत के आकार की भविष्यवाणी करने में सक्षम नहीं होगा, limitजबकि इस तरह की भविष्यवाणी को रोकने के लिए कोई मौलिक सीमा नहीं है।

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

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

तो सबसे उपयोगी चीज जो आप कर सकते हैं peekवह यह पता लगाना है कि क्या स्ट्रीम तत्व को संसाधित किया गया है जो कि एपीआई प्रलेखन कहता है:

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


क्या ओपी के उपयोग के मामले में कोई समस्या, भविष्य या वर्तमान होगा? क्या उसका कोड हमेशा वही करता है जो वह चाहता है?
18Y पर झोंग्यू

9
@ bayou.io: जहां तक ​​मैं देख सकता हूं, इस सटीक रूप में कोई समस्या नहीं है । लेकिन जैसा कि मैंने समझाने की कोशिश की, इसका उपयोग इस तरह से किया गया है कि आपको इस पहलू को याद रखना होगा, भले ही आप कोड में एक या दो साल बाद कोड में «सुविधा अनुरोध 9876» शामिल करने के लिए वापस आ जाएं ...
होल्गर

1
"मनमाने ढंग से आदेश और समवर्ती रूप से झांकने की कार्रवाई हो सकती है"। क्या यह कथन उनके नियम के खिलाफ नहीं जाता है कि कैसे काम करता है, जैसे "तत्वों का सेवन किया जाता है"?
जोस मार्टिनेज

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

23

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

return list.stream().map(foo->foo.getBar())
                    .peek(bar->bar.publish("HELLO"))
                    .collect(Collectors.toList());

लगता है कि एक वैध मामला है जहाँ आप चाहते हैं, एक ऑपरेशन में सभी Foos को बार्स में बदलने के लिए और उन्हें सभी हैलो बताएं।

कुछ की तुलना में अधिक कुशल और सुरुचिपूर्ण लगता है:

List<Bar> bars = list.stream().map(foo->foo.getBar()).collect(Collectors.toList());
bars.forEach(bar->bar.publish("HELLO"));
return bars;

और आप एक संग्रह को दो बार पुनरावृत्त नहीं करते हैं।


4

मैं कहूंगा कि कोडpeek को विकेंद्रीकृत करने की क्षमता प्रदान करता है जो वस्तुओं को प्रवाहित कर सकता है, या वैश्विक स्थिति को संशोधित कर सकता है (उन पर आधारित), जो कि एक टर्मिनल विधि को पारित किए गए एक साधारण या संयोजित फ़ंक्शन में सब कुछ भरने के बजाय ।

अब सवाल यह हो सकता है: क्या हमें कार्यात्मक शैली जावा प्रोग्रामिंग में कार्यों से भीतर वस्तुओं को बदलना या वैश्विक स्थिति को बदलना चाहिए ?

यदि उपरोक्त 2 से किसी भी प्रश्न का जवाब है हाँ (या: कुछ मामलों हाँ में) तो peek()है निश्चित रूप से न केवल डीबगिंग उद्देश्यों के लिए , एक ही कारण है कि के लिए forEach()है न केवल डीबगिंग उद्देश्यों के लिए

मेरे बीच चयन करते समय forEach()और peek(), निम्नलिखित का चयन कर रहा है: क्या मुझे कोड के टुकड़े चाहिए जो एक स्ट्रीम करने योग्य स्ट्रीम ऑब्जेक्ट्स को किसी कंपोजेबल के साथ जोड़ा जाए, या क्या मैं चाहता हूं कि वे सीधे स्ट्रीम में संलग्न हों?

मुझे लगता है कि peek()जावा 9 तरीकों के साथ बेहतर जोड़ी होगी। उदाहरण के takeWhile()लिए यह तय करने की आवश्यकता हो सकती है कि पहले से ही उत्परिवर्तित वस्तु के आधार पर पुनरावृत्ति को कब रोकना है, इसलिए इसे forEach()समान प्रभाव नहीं पड़ेगा।

PS मैंने map()कहीं भी संदर्भित नहीं किया है क्योंकि अगर हम नई वस्तुओं को बनाने के बजाय वस्तुओं (या वैश्विक स्थिति) को बदलना चाहते हैं, तो यह बिल्कुल उसी तरह काम करता है peek()


3

यद्यपि मैं ऊपर दिए गए अधिकांश उत्तरों से सहमत हूं, मेरे पास एक मामला है जिसमें झांकना वास्तव में उपयोग करने का सबसे साफ तरीका है।

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

accounts.stream()
    .filter(Account::isActive)
    .peek(login)
    .collect(Collectors.toList());

दो बार संग्रह को पुनरावृत्त नहीं करने के लिए पीक बेमानी कॉल से बचने में मददगार है:

accounts.stream()
    .filter(Account::isActive)
    .map(account -> {
        account.login();
        return account;
    })
    .collect(Collectors.toList());

3
आपको बस उस लॉगिन विधि को ठीक करना है। मैं वास्तव में नहीं देखता कि कैसे जाने का सबसे साफ तरीका है। आपके कोड को पढ़ने वाला कोई व्यक्ति कैसे जानता है कि आप वास्तव में एपीआई का दुरुपयोग कर रहे हैं। अच्छा और साफ कोड किसी पाठक को कोड के बारे में धारणा बनाने के लिए मजबूर नहीं करता है।
काबा k१३

1

कार्यात्मक समाधान खाता वस्तु को अपरिवर्तनीय बनाना है। तो account.login () को एक नया खाता ऑब्जेक्ट वापस करना होगा। इसका मतलब यह होगा कि मैप ऑपरेशन का इस्तेमाल झांकने के बजाय लॉगिन के लिए किया जा सकता है।

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