जावा 8 Iterable.forEach () बनाम फॉरेस्ट लूप


463

जावा 8 में निम्नलिखित में से कौन सा बेहतर अभ्यास है?

जावा 8:

joins.forEach(join -> mIrc.join(mSession, join));

जावा 7:

for (String join : joins) {
    mIrc.join(mSession, join);
}

मेरे पास लूप के लिए बहुत सारे हैं जो कि लैम्ब्डा के साथ "सरलीकृत" हो सकते हैं, लेकिन क्या वास्तव में उनका उपयोग करने का कोई फायदा है? क्या यह उनके प्रदर्शन और पठनीयता में सुधार करेगा?

संपादित करें

मैं इस प्रश्न को लंबी विधियों तक बढ़ाता हूँ। मुझे पता है कि आप एक लंबोदर से माता-पिता के कार्य को वापस नहीं कर सकते या तोड़ नहीं सकते हैं और उनकी तुलना करते समय यह भी ध्यान में रखा जाना चाहिए, लेकिन क्या कुछ और माना जाना चाहिए?


10
एक दूसरे के ऊपर वास्तविक प्रदर्शन लाभ नहीं है। पहला विकल्प एफपी से प्रेरित कुछ है (चुड़ैल को आमतौर पर "अच्छा" और "स्पष्ट" अपने कोड को व्यक्त करने के तरीके के बारे में बात की जाती है)। वास्तविकता में - यह बल्कि "शैली" प्रश्न है।
यूजीन लोय

5
@ डब्लूबी: इस मामले में, यह प्रासंगिक नहीं है। forEach को समानांतर या ऐसा कुछ होने के रूप में परिभाषित नहीं किया गया है, इसलिए ये दोनों चीजें शब्दार्थ के समान हैं। बेशक यह forEach (और एक पहले से ही मानक पुस्तकालय में मौजूद हो सकता है) के समानांतर संस्करण को लागू करना संभव है, और इस तरह के मामले में लैम्ब्डा अभिव्यक्ति वाक्यविन्यास बहुत उपयोगी होगा।
अनारदवरकसप

8
@AardvarkSoup जिस उदाहरण पर forEach कहा जाता है वह एक स्ट्रीम ( lambdadoc.net/api/java/util/stream/Stream.html ) है। समानांतर निष्पादन का अनुरोध करने के लिए एक व्यक्ति joins.parallel () forEach (...) लिख सकता है
mschenk74

13
क्या joins.forEach((join) -> mIrc.join(mSession, join));वास्तव में "सरलीकरण" है for (String join : joins) { mIrc.join(mSession, join); }? के प्रकार को छुपाने के लाभ के लिए आपने विराम चिह्न की संख्या 9 से बढ़ाकर 12 कर दी है join। आपने वास्तव में जो किया है वह दो बयानों को एक पंक्ति में रखना है।
टॉम हॉल्टिन -

7
विचार करने के लिए एक और बिंदु जावा की सीमित परिवर्तनीय कैप्चर क्षमता है। Stream.forEach () के साथ, आप स्थानीय चर को अपडेट नहीं कर सकते हैं क्योंकि उनका कब्जा उन्हें अंतिम बनाता है, जिसका अर्थ है कि आपके पास forbach lambda (जब तक कि आप कुछ बदसूरती के लिए तैयार नहीं होते हैं जैसे कि क्लास स्टेट वैरिएबल का उपयोग कर रहे हैं)।
RedGlyph

जवाबों:


509

बेहतर अभ्यास का उपयोग करना है for-eachकीप इट सिंपल, स्टूपिड सिद्धांत का उल्लंघन करने के अलावा , नए-फ्रैंगल्ड forEach()में कम से कम निम्नलिखित कमियां हैं:

  • गैर-अंतिम चर का उपयोग नहीं कर सकते । इसलिए, निम्न की तरह कोड को फॉरवर्ड लैम्बडा में नहीं बदला जा सकता है:

    Object prev = null;
    for(Object curr : list)
    {
        if( prev != null )
            foo(prev, curr);
        prev = curr;
    }
  • जाँच किए गए अपवादों को संभाल नहीं सकते । लैंबडास को वास्तव में चेक किए गए अपवादों को फेंकने से मना नहीं किया जाता है, लेकिन आम कार्यात्मक इंटरफेस जैसे Consumerकोई भी घोषित नहीं करता है। इसलिए, जाँच किए गए अपवादों को फेंकने वाला कोई भी कोड उन्हें try-catchया में लपेटना होगा Throwables.propagate()। लेकिन अगर आप ऐसा करते हैं, तो यह हमेशा स्पष्ट नहीं होता है कि फेंके गए अपवाद का क्या होता है। यह कहीं न कहीं के गम में निगल सकता हैforEach()

  • सीमित प्रवाह-नियंत्रण । एक returnलंबोदर continueमें एक प्रत्येक के लिए एक के बराबर है, लेकिन एक के बराबर नहीं है break। रिटर्न वैल्यू, शॉर्ट सर्किट, या सेट फ्लैग जैसी चीजें करना भी मुश्किल है (जो कि चीज़ों को थोड़ा कम कर देता, अगर यह बिना किसी अंतिम चर नियम का उल्लंघन नहीं होता) करना । "यह केवल एक अनुकूलन नहीं है, लेकिन महत्वपूर्ण है जब आप समझते हैं कि कुछ अनुक्रम (जैसे फ़ाइल में लाइनों को पढ़ना) के दुष्प्रभाव हो सकते हैं, या आपके पास एक अनंत अनुक्रम हो सकता है।"

  • समानांतर में निष्पादित हो सकता है , जो सभी के लिए एक भयानक, भयानक चीज है, लेकिन आपके कोड का 0.1% जिसे अनुकूलित करने की आवश्यकता है। किसी भी समानांतर कोड के माध्यम से सोचा जाना चाहिए (भले ही यह ताले, वाष्पशील और पारंपरिक बहु-थ्रेडेड निष्पादन के अन्य विशेष रूप से बुरा पहलुओं का उपयोग नहीं करता है)। किसी भी बग को ढूंढना मुश्किल होगा।

  • प्रदर्शन को चोट लग सकती है , क्योंकि जेआईटी फॉरवर्ड () + लाम्बा को सादे छोरों के समान सीमा तक अनुकूलित नहीं कर सकता है, विशेषकर अब यह कि लैम्ब्डा नए हैं। "अनुकूलन" से मेरा तात्पर्य लैम्बदास (जो छोटा है) को बुलाने से नहीं है, लेकिन परिष्कृत विश्लेषण और परिवर्तन से है कि आधुनिक जेआईटी कंपाइलर रनिंग कोड पर कार्य करता है।

  • यदि आपको समानता की आवश्यकता है, तो यह संभवतः बहुत तेज़ है और एक्सक्यूसोर सर्विस का उपयोग करने के लिए अधिक कठिन नहीं है । स्ट्रीम दोनों हैं automagical (पढ़ें: ज्यादा अपनी समस्या के बारे पता नहीं है) और एक विशेष (पढ़ें: सामान्य स्थिति के लिए अक्षम) का उपयोग बनता रणनीति ( पुनरावर्ती अपघटन कांटा-में शामिल होने के ) का उपयोग करें।

  • अधिक भ्रामक डीबगिंग बनाता हैनेस्टेड कॉल पदानुक्रम और, ईश्वर की मनाही, समानांतर निष्पादन के कारण । डिबगर में आसपास के कोड से चर प्रदर्शित करने के मुद्दे हो सकते हैं, और स्टेप-थ्रू जैसी चीजें उम्मीद के मुताबिक काम नहीं कर सकती हैं।

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

  • गले में खराश की तरह चुभती है । जावा भाषा में पहले से ही प्रत्येक कथन है। इसे फ़ंक्शन कॉल के साथ क्यों बदलें? कहीं-कहीं भावों में छिपने के दुष्परिणाम को क्यों प्रोत्साहित करते हैं? अलौकिक वन-लाइनर्स को क्यों प्रोत्साहित करें? नियमित रूप से प्रत्येक और नए forEach विली-नीली को मिलाना बुरी शैली है। कोड को मुहावरों में बोलना चाहिए (पैटर्न जो उनके पुनरावृत्ति के कारण समझने के लिए त्वरित हैं), और कम मुहावरों का उपयोग उस कोड को साफ करने के लिए किया जाता है और कम समय यह तय करने में बिताया जाता है कि किस मुहावरे का उपयोग करें (अपने जैसे पूर्णतावादियों के लिए एक बड़ा समय-नाली! )।

जैसा कि आप देख सकते हैं, मैं forEach का एक बड़ा प्रशंसक नहीं हूँ () मामलों में छोड़कर जब यह समझ में आता है।

मेरे लिए विशेष रूप से अपमानजनक तथ्य यह Streamहै कि लागू नहीं होता है Iterable(वास्तव में विधि होने के बावजूद iterator) और प्रत्येक के लिए, केवल forEach () के साथ उपयोग नहीं किया जा सकता है। मैं के साथ Iterables में स्ट्रीम कास्टिंग की सलाह देता हूं (Iterable<T>)stream::iterator। एक बेहतर विकल्प स्ट्रीमटेक्स का उपयोग करना है जो कई एपीआई एपीआई समस्याओं को ठीक करता है, जिसमें कार्यान्वयन भी शामिल है Iterable

उस ने कहा, forEach()निम्नलिखित के लिए उपयोगी है:

  • Atomically एक सिंक्रनाइज़ सूची में पुनरावृत्ति । इससे पहले, एक सूची के साथ उत्पन्न सूची Collections.synchronizedList()प्राप्त या सेट जैसी चीजों के संबंध में परमाणु थी, लेकिन पुनरावृति करते समय थ्रेड-सुरक्षित नहीं थी।

  • समानांतर निष्पादन (एक उपयुक्त समानांतर धारा का उपयोग करके) । यदि आप अपनी समस्या को स्ट्रीम और स्प्लिटेटर में निर्मित प्रदर्शन मान्यताओं से मेल खाते हैं, तो यह आपको एक्सेकॉर्स सर्विस का उपयोग करके कोड की कुछ पंक्तियों को बचाता है।

  • विशिष्ट कंटेनर , जो सिंक्रनाइज़ की गई सूची की तरह, पुनरावृत्ति के नियंत्रण में होने से लाभ (हालांकि यह काफी हद तक सैद्धांतिक है जब तक कि लोग इसे और अधिक नहीं ला सकते)

  • एकल फ़ंक्शन का उपयोग करके forEach()और विधि संदर्भ तर्क (यानी, list.forEach (obj::someMethod)) द्वारा अधिक सफाई से कॉल करना । हालांकि, चेक किए गए अपवादों, अधिक कठिन डिबगिंग और अंक लिखते समय आपके द्वारा उपयोग किए जाने वाले मुहावरों की संख्या को कम करने पर ध्यान रखें।

लेख मैं संदर्भ के लिए इस्तेमाल किया:

संपादित करें: लैंबडास के कुछ मूल प्रस्तावों (जैसे http://www.javac.info/closures-v06a.html ) ने मेरे द्वारा बताए गए कुछ मुद्दों को हल किया (जबकि अपनी जटिलताओं को जोड़ते हुए, निश्चित रूप से)।


95
"अभिव्यक्ति में कहीं न कहीं साइड-इफ़ेक्ट को क्यों प्रोत्साहित किया जाए?" गलत सवाल है। कार्यात्मक forEach, बिना पक्ष-प्रभाव के अभिव्यक्ति का उपयोग करते हुए कार्यात्मक शैली को प्रोत्साहित करने के लिए है । यदि आप स्थिति का सामना करते हैं, तो forEachआपके दुष्प्रभावों के साथ अच्छी तरह से काम नहीं करता है आपको यह महसूस करना चाहिए कि आपका काम के लिए सही उपकरण का उपयोग नहीं कर रहा है। तब सरल उत्तर है, ऐसा इसलिए है क्योंकि आपकी भावना सही है, इसलिए उसके लिए प्रत्येक लूप में रहें। शास्त्रीय forलूप पदावनत नहीं हुआ ...
Holger

17
@ हॉल्जर forEachबिना साइड-इफेक्ट्स के कैसे इस्तेमाल किया जा सकता है?
डबिन्स्की

14
सब ठीक है, मैं पर्याप्त सटीक नहीं था, forEachसाइड-इफेक्ट के लिए एकमात्र स्ट्रीम ऑपरेशन है, लेकिन यह आपके उदाहरण कोड जैसे साइड-इफेक्ट के लिए नहीं है, गिनती एक विशिष्ट reduceऑपरेशन है। मैं सुझाव दूंगा, एक नियम के रूप में, प्रत्येक ऑपरेशन को रखने के लिए जो स्थानीय चर को हेरफेर करता है या एक शास्त्रीय forलूप में नियंत्रण प्रवाह (झुकाव अपवाद हैंडलिंग) को प्रभावित करेगा । मुझे लगता है कि मूल सवाल के बारे में, समस्या इस तथ्य से उपजी है कि कोई एक धारा का उपयोग करता है जहां धारा forके स्रोत पर एक साधारण लूप पर्याप्त होगा। एक धारा का उपयोग करें जहाँ forEach()केवल काम करता है
Holger

8
@ होल्गर साइड-इफेक्ट्स का एक उदाहरण क्या है जो इसके लिए forEachउपयुक्त होगा?
१०:०५ पर अलेक्सांद्र डबिन्सकी

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

169

फायदा तब होता है जब ऑपरेशन को समानांतर में निष्पादित किया जा सकता है। ( Http://java.dzone.com/articles/devoxx-2012-java-8-lambda-and - आंतरिक और बाहरी पुनरावृत्ति के बारे में अनुभाग देखें )

  • मेरे दृष्टिकोण से मुख्य लाभ यह है कि लूप के भीतर जो किया जाना है उसे लागू किए बिना यह तय करने के लिए परिभाषित किया जा सकता है कि क्या इसे समानांतर या अनुक्रमिक रूप से निष्पादित किया जाएगा?

  • यदि आप चाहते हैं कि आपके पाश को समानांतर में निष्पादित किया जाए तो आप बस लिख सकते हैं

     joins.parallelStream().forEach(join -> mIrc.join(mSession, join));

    आपको थ्रेड हैंडलिंग आदि के लिए कुछ अतिरिक्त कोड लिखना होगा।

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


4
एक ओरेकल इंजीनियर की स्लाइड्स वह ( blogs.oracle.com/darcy/resource/Devoxx/… ) उन लंबोदर भावों के भीतर समानता का उल्लेख नहीं करता है। समानता संग्रह के तरीकों के भीतर हो सकती है जैसे mapऔर foldजो वास्तव में लंबोदर से संबंधित नहीं हैं।
थॉमस जुंगब्लेट

1
यह वास्तव में प्रतीत नहीं होता है कि ओपी का कोड यहां स्वचालित समानता से लाभान्वित होगा (विशेषकर चूंकि कोई गारंटी नहीं है कि एक होगा)। हम वास्तव में नहीं जानते हैं कि "mIrc" क्या है, लेकिन "जॉइन" वास्तव में ऐसा कुछ नहीं लगता है जो आउट-ऑफ-ऑर्डर हो सकता है।
यूजीन लोय

10
Stream#forEachऔर Iterable#forEachएक ही बात नहीं कर रहे हैं। ओपी के बारे में पूछ रहे हैं Iterable#forEach
ग्वलासोव

2
मैंने UPDATEX शैली का उपयोग किया क्योंकि प्रश्न पूछे जाने के समय और जवाब अपडेट होने के समय के बीच विनिर्देश में परिवर्तन थे। उत्तर के इतिहास के बिना यह और भी भ्रामक होगा जो मैंने सोचा था।
mschenk74

1
क्या कोई मुझे समझा सकता है कि यह जवाब मान्य नहीं है यदि इसके बजाय joinsलागू Iterableकिया जा रहा है Stream? कुछ चीजें जो मैंने पढ़ी हैं, joins.stream().forEach((join) -> mIrc.join(mSession, join));joins.parallelStream().forEach((join) -> mIrc.join(mSession, join));joinsIterable
उनमें से

112

इस प्रश्न को पढ़ते समय किसी को यह आभास हो सकता है, कि Iterable#forEachलंबोदर के साथ संयोजन में प्रत्येक लूप के लिए पारंपरिक लिखने के लिए एक शॉर्टकट / प्रतिस्थापन है। यह बिल्कुल सही नहीं है। ओपी का यह कोड:

joins.forEach(join -> mIrc.join(mSession, join));

है लिखने के लिए एक शॉर्टकट के रूप में इरादा

for (String join : joins) {
    mIrc.join(mSession, join);
}

और निश्चित रूप से इस तरह से उपयोग नहीं किया जाना चाहिए। इसके बजाय यह एक शॉर्टकट के रूप में इरादा है (हालांकि यह लिखने के लिए बिल्कुल वैसा ही नहीं है)

joins.forEach(new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
});

और यह निम्नलिखित जावा 7 कोड के प्रतिस्थापन के रूप में है:

final Consumer<T> c = new Consumer<T>() {
    @Override
    public void accept(T join) {
        mIrc.join(mSession, join);
    }
};
for (T t : joins) {
    c.accept(t);
}

एक लूप के शरीर को एक कार्यात्मक इंटरफ़ेस के साथ बदलना, जैसा कि ऊपर दिए गए उदाहरणों में, आपके कोड को और अधिक स्पष्ट करता है: आप कह रहे हैं कि (1) लूप का शरीर आसपास के कोड और नियंत्रण प्रवाह को प्रभावित नहीं करता है, और (2) लूप के शरीर को फ़ंक्शन के एक अलग कार्यान्वयन के साथ बदला जा सकता है, आसपास के कोड को प्रभावित किए बिना। बाहरी दायरे के गैर अंतिम चर तक नहीं पहुंच पाने के कारण कार्यों / लैंबडों की कमी नहीं है, यह एक विशेषता है जो Iterable#forEachपारंपरिक लूप के शब्दार्थ से प्रत्येक लूप के शब्दार्थ को अलग करती है। एक बार एक वाक्य रचना के लिए इस्तेमाल किया जाता हैIterable#forEach , यह कोड को अधिक पठनीय बनाता है, क्योंकि आपको तुरंत कोड के बारे में यह अतिरिक्त जानकारी मिल जाती है।

प्रत्येक लूप के लिए पारंपरिक रूप से जावा में निश्चित रूप से अच्छा अभ्यास (अति प्रयोग " सर्वोत्तम अभ्यास " से बचने के लिए ) रहेगा । लेकिन इसका मतलब यह नहीं है कि Iterable#forEachइसे बुरा व्यवहार या बुरी शैली माना जाना चाहिए। यह हमेशा अच्छा अभ्यास है, काम करने के लिए सही उपकरण का उपयोग करने के लिए, और इसमें प्रत्येक के लिए पारंपरिक मिश्रण को शामिल करना शामिल हैIterable#forEach , जहां यह समझ में आता है।

चूँकि Iterable#forEachइस धागे में पहले से ही गिरावट की चर्चा की गई है, यहाँ कुछ कारण दिए गए हैं, आप शायद इसका उपयोग क्यों करना चाहते हैं Iterable#forEach:

  • अपने कोड अधिक स्पष्ट करने के लिए: के रूप में ऊपर वर्णित है, Iterable#forEach कर सकते हैं अपने कोड अधिक स्पष्ट और कुछ स्थितियों में पठनीय हैं।

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

    joins.forEach(getJoinStrategy());

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

  • अपने कोड को अधिक डीबग करने योग्य बनाने के लिए: घोषणा से लूप कार्यान्वयन को अलग करना भी डीबगिंग को अधिक आसान बना सकता है, क्योंकि आपके पास एक विशेष डीबग कार्यान्वयन हो सकता है, जो डिबग संदेशों को प्रिंट करता है, बिना आपके मुख्य कोड को अव्यवस्थित करने के लिए if(DEBUG)System.out.println()। डिबग कार्यान्वयन जैसे एक हो सकता है प्रतिनिधि , कि सजाया गया वास्तविक समारोह कार्यान्वयन।

  • प्रदर्शन-महत्वपूर्ण कोड को ऑप्टिमाइज़ करने के लिए: इस थ्रेड में कुछ कथनों के विपरीत, पहले से ही प्रत्येक पारंपरिक लूप की तुलना में बेहतर प्रदर्शन प्रदान Iterable#forEach करता है , कम से कम जब ArrayList का उपयोग करते हुए और हॉटस्पॉट को "-client" मोड में चलाते हैं। हालांकि यह प्रदर्शन बूस्ट छोटा है और अधिकांश उपयोग के मामलों के लिए नगण्य है, ऐसी स्थितियां हैं, जहां यह अतिरिक्त प्रदर्शन अंतर ला सकता है। उदाहरण के लिए पुस्तकालय के रखवाले निश्चित रूप से मूल्यांकन करना चाहेंगे, यदि उनके कुछ मौजूदा लूप कार्यान्वयन को प्रतिस्थापित किया जाना चाहिएIterable#forEach

    इस कथन को तथ्यों के साथ वापस लेने के लिए, मैंने कैलिपर के साथ कुछ माइक्रो-बेंचमार्क किए हैं । यहाँ परीक्षण कोड है (git से नवीनतम कैलीपर की आवश्यकता है):

    @VmOptions("-server")
    public class Java8IterationBenchmarks {
    
        public static class TestObject {
            public int result;
        }
    
        public @Param({"100", "10000"}) int elementCount;
    
        ArrayList<TestObject> list;
        TestObject[] array;
    
        @BeforeExperiment
        public void setup(){
            list = new ArrayList<>(elementCount);
            for (int i = 0; i < elementCount; i++) {
                list.add(new TestObject());
            }
            array = list.toArray(new TestObject[list.size()]);
        }
    
        @Benchmark
        public void timeTraditionalForEach(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : list) {
                    t.result++;
                }
            }
            return;
        }
    
        @Benchmark
        public void timeForEachAnonymousClass(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(new Consumer<TestObject>() {
                    @Override
                    public void accept(TestObject t) {
                        t.result++;
                    }
                });
            }
            return;
        }
    
        @Benchmark
        public void timeForEachLambda(int reps){
            for (int i = 0; i < reps; i++) {
                list.forEach(t -> t.result++);
            }
            return;
        }
    
        @Benchmark
        public void timeForEachOverArray(int reps){
            for (int i = 0; i < reps; i++) {
                for (TestObject t : array) {
                    t.result++;
                }
            }
        }
    }

    और यहाँ परिणाम हैं:

    जब "-client" के साथ चल रहा है, Iterable#forEachएक ArrayList पर लूप के लिए पारंपरिक को बेहतर बनाता है, लेकिन एक सरणी पर सीधे चलने की तुलना में अभी भी धीमा है। "-Server" के साथ चलने पर, सभी दृष्टिकोणों का प्रदर्शन लगभग एक जैसा होता है।

  • समानांतर निष्पादन के लिए वैकल्पिक सहायता प्रदान करने के लिए: यह पहले से ही यहां कहा गया है, कि धाराओंIterable#forEach का उपयोग करके समानांतर में कार्यात्मक इंटरफ़ेस को निष्पादित करने की संभावना , निश्चित रूप से एक महत्वपूर्ण पहलू है। चूंकि यह गारंटी नहीं देता है कि लूप वास्तव में समानांतर में निष्पादित किया जाता है, इसलिए किसी को इसे एक वैकल्पिक विशेषता पर विचार करना चाहिए । अपनी सूची के साथ पुनरावृत्ति करके , आप स्पष्ट रूप से कहते हैं: यह लूप समर्थन करता हैCollection#parallelStream()list.parallelStream().forEach(...); समानांतर निष्पादन है, लेकिन यह इस पर निर्भर नहीं करता है। फिर, यह एक विशेषता है और घाटा नहीं है!

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

    public abstract class MyOptimizedCollection<E> implements Collection<E>{
        private enum OperatingSystem{
            LINUX, WINDOWS, ANDROID
        }
        private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
        private int numberOfCores = Runtime.getRuntime().availableProcessors();
        private Collection<E> delegate;
    
        @Override
        public Stream<E> parallelStream() {
            if (!System.getProperty("parallelSupport").equals("true")) {
                return this.delegate.stream();
            }
            switch (operatingSystem) {
                case WINDOWS:
                    if (numberOfCores > 3 && delegate.size() > 10000) {
                        return this.delegate.parallelStream();
                    }else{
                        return this.delegate.stream();
                    }
                case LINUX:
                    return SomeVerySpecialStreamImplementation.stream(this.delegate.spliterator());
                case ANDROID:
                default:
                    return this.delegate.stream();
            }
        }
    }

    यहाँ अच्छी बात यह है कि, आपके लूप कार्यान्वयन को इन विवरणों के बारे में जानने या देखभाल करने की आवश्यकता नहीं है।


5
इस चर्चा में आपके पास एक दिलचस्प दृष्टिकोण है और कई बिंदुओं को सामने लाता है। मैं उन्हें संबोधित करने की कोशिश करूंगा। आप के बीच स्विच करने का प्रस्ताव forEachऔर for-eachपाश शरीर की प्रकृति के बारे में कुछ मानदंडों के आधार पर। ऐसे नियमों का पालन करने के लिए समझदारी और अनुशासन एक अच्छे प्रोग्रामर की पहचान है। इस तरह के नियम भी उसके प्रतिबंध हैं, क्योंकि उसके आसपास के लोग या तो उनका अनुसरण नहीं करते हैं या असहमत हैं। उदाहरण के लिए, अनियंत्रित अपवाद बनाम चेक का उपयोग करना। यह स्थिति और भी अधिक बारीक लगती है। लेकिन, अगर शरीर "सराउंड कोड या प्रवाह नियंत्रण को प्रभावित नहीं करता है," क्या यह एक फ़ंक्शन के रूप में बेहतर नहीं है?
अलेक्सांद्र डबिन्सकी

4
विस्तृत टिप्पणियों के लिए धन्यवाद। But, if the body "does not affect surround code or flow control," isn't factoring it out as a function better?। हाँ, यह अक्सर मेरी राय में होगा - इन छोरों को फैक्टरिंग करना क्योंकि फ़ंक्शन एक प्राकृतिक परिणाम है।
बाल्डर

2
प्रदर्शन के मुद्दे के बारे में - मुझे लगता है कि यह लूप की प्रकृति पर बहुत निर्भर करता है। मैं जिस प्रोजेक्ट पर काम कर रहा हूं, मैं Iterable#forEachपरफॉर्मेंस बढ़ाने की वजह से जावा 8 से पहले फंक्शन-स्टाइल लूप्स का इस्तेमाल कर रहा हूं । प्रश्न में इस परियोजना में गेम लूप के समान एक मुख्य लूप है, जिसमें नस्टेड सब-लूप की अपरिभाषित संख्या होती है, जहां क्लाइंट फंक्शन में लूप प्रतिभागियों को प्लग-इन कर सकते हैं। इस तरह के एक सॉफ्टवेयर संरचना से बहुत लाभ होता है Iteable#forEach
बाल्डर

7
मेरे समालोचना के बहुत अंत में एक वाक्य है: "कोड को मुहावरों में बोलना चाहिए, और कम मुहावरों का उपयोग उस कोड को साफ करने के लिए किया जाता है और कम समय यह तय करने में बिताया जाता है कि किस मुहावरे का उपयोग करना है"। जब मैंने C # से जावा में स्विच किया तो मैंने इस बिंदु की गहराई से सराहना करना शुरू कर दिया।
Aleksandr Dubinsky

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

13

forEach()प्रत्येक लूप की तुलना में तेज़ होने के लिए कार्यान्वित किया जा सकता है, क्योंकि iterable अपने तत्वों को पुनरावृत्त करने का सबसे अच्छा तरीका जानता है, जैसा कि मानक पुनरावृत्त तरीके के विपरीत है। तो अंतर आंतरिक रूप से लूप है या बाहरी रूप से लूप।

उदाहरण के लिए ArrayList.forEach(action)बस के रूप में लागू किया जा सकता है

for(int i=0; i<size; i++)
    action.accept(elements[i])

के लिए के रूप में प्रत्येक पाश जो मचान की बहुत आवश्यकता के लिए विरोध किया

Iterator iter = list.iterator();
while(iter.hasNext())
    Object next = iter.next();
    do something with `next`

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

विभिन्न उपयोग के मामलों के लिए आंतरिक / बाह्य पुनरावृत्तियों की तुलना करने के लिए http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/ भी देखें ।


8
क्यों iterable सबसे अच्छा तरीका जानता है, लेकिन itter नहीं करता है?
mschenk74

2
कोई आवश्यक अंतर नहीं है, लेकिन इटेरियर इंटरफ़ेस के अनुरूप अतिरिक्त कोड की आवश्यकता होती है, जो अधिक महंगा हो सकता है।
झोंगयू

1
@ zhong.j.yu यदि आप कलेक्शन लागू करते हैं तो आप वैसे भी Iterable को लागू कर सकते हैं। तो, "यदि आपकी बात याद आ रही है, तो" लापता इंटरफ़ेस विधियों को लागू करने के लिए और अधिक कोड जोड़ने "के संदर्भ में कोई कोड ओवरहेड नहीं है। जैसा कि mschenk74 ने कहा कि ऐसा कोई कारण नहीं है कि आप अपने संग्रहकर्ता को यह जानने के लिए बाध्य न कर सकें कि आपके संग्रह को सबसे अच्छे तरीके से कैसे बनाया जाए। मैं इस बात से सहमत हूं कि पुनरावृत्ति निर्माण के लिए ओवरहेड हो सकता है, लेकिन गंभीरता से, वे चीजें आमतौर पर इतनी सस्ती होती हैं, कि आप कह सकते हैं कि उनकी शून्य लागत है ...
यूजीन लोय

4
उदाहरण के लिए एक पेड़ की पुनरावृत्ति:, void forEach(Consumer<T> v){leftTree.forEach(v);v.accept(rootElem);rightTree.forEach(v);}यह बाहरी पुनरावृत्ति की तुलना में अधिक सुंदर है, और आप यह तय कर सकते हैं कि कैसे सबसे अच्छा सिंक्रनाइज़ किया जाए
शाफ़्ट सनकी

1
मजेदार रूप से पर्याप्त है, String.joinविधियों में एकमात्र टिप्पणी (ठीक है, गलत जुड़ना) "तत्वों की संख्या संभवतया Arrays -stream माथे के लायक नहीं है।" इसलिए वे पाश के लिए एक पॉश का उपयोग करते हैं।
टॉम हॉकिन -

7

TL; DR : List.stream().forEach()सबसे तेज था।

मुझे लगा कि मुझे अपने परिणामों को बेंचमार्किंग पुनरावृत्ति से जोड़ना चाहिए। मैंने एक बहुत ही सरल तरीका अपनाया (कोई बेंचमार्किंग फ्रेमवर्क नहीं) और 5 अलग-अलग तरीकों को बेंचमार्क किया:

  1. क्लासिक for
  2. क्लासिक फोरच
  3. List.forEach()
  4. List.stream().forEach()
  5. List.parallelStream().forEach

परीक्षण प्रक्रिया और मापदंडों

private List<Integer> list;
private final int size = 1_000_000;

public MyClass(){
    list = new ArrayList<>();
    Random rand = new Random();
    for (int i = 0; i < size; ++i) {
        list.add(rand.nextInt(size * 50));
    }    
}
private void doIt(Integer i) {
    i *= 2; //so it won't get JITed out
}

इस वर्ग की सूची को इससे अधिक पुनरावृत्त किया जाएगा और कुछ doIt(Integer i)को सभी सदस्यों के लिए लागू किया जाएगा, प्रत्येक बार एक अलग विधि के माध्यम से। मुख्य कक्षा में मैं जेवीएम को गर्म करने के लिए तीन बार परीक्षण विधि चलाता हूं। फिर मैं प्रत्येक पुनरावृत्ति विधि (उपयोग करते हुए System.nanoTime()) के लिए परीक्षण विधि को 1000 बार जोड़ देता हूं । उसके बाद मैंने उस राशि को 1000 से विभाजित किया और उसका परिणाम, औसत समय। उदाहरण:

myClass.fored();
myClass.fored();
myClass.fored();
for (int i = 0; i < reps; ++i) {
    begin = System.nanoTime();
    myClass.fored();
    end = System.nanoTime();
    nanoSum += end - begin;
}
System.out.println(nanoSum / reps);

मैंने इसे i5 4 कोर सीपीयू पर चलाया, जावा संस्करण 1.8.0_05 के साथ

क्लासिक for

for(int i = 0, l = list.size(); i < l; ++i) {
    doIt(list.get(i));
}

निष्पादन समय: 4.21 एमएस

क्लासिक फोरच

for(Integer i : list) {
    doIt(i);
}

निष्पादन समय: 5.95 एमएस

List.forEach()

list.forEach((i) -> doIt(i));

निष्पादन समय: 3.11 एमएस

List.stream().forEach()

list.stream().forEach((i) -> doIt(i));

निष्पादन समय: 2.79 एमएस

List.parallelStream().forEach

list.parallelStream().forEach((i) -> doIt(i));

निष्पादन समय: 3.6 एमएस


23
आप उन नंबरों को कैसे प्राप्त करेंगे? बेंचमार्क के लिए आप किस फ्रेमवर्क का उपयोग कर रहे हैं? यदि आप System.out.printlnइस डेटा को भोलेपन से प्रदर्शित करने के लिए कोई नहीं और सिर्फ सादा उपयोग कर रहे हैं , तो सभी परिणाम बेकार हैं।
लुइगी मेंडोज़ा

2
कोई ढांचा नहीं। मैं उपयोग करता हूं System.nanoTime()। यदि आप उत्तर पढ़ते हैं तो आप देखेंगे कि यह कैसे किया गया। मुझे नहीं लगता कि यह देखना बेकार है क्योंकि यह एक सापेक्ष प्रश्न है। मुझे परवाह नहीं है कि एक निश्चित विधि ने कितना अच्छा किया, मुझे परवाह है कि अन्य तरीकों की तुलना में यह कितना अच्छा है।
असफ

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

6
मैं इसके बजाय JMH को जानने की सलाह दे सकता हूं, यह वही है जो जावा के लिए उपयोग किया जा रहा है और सही संख्या प्राप्त करने के लिए बहुत प्रयास करता है: Openjdk.java.net/projects/code-tools/jmh
dsvenshe

1
मैं @LuiggiMendoza से सहमत हूं। यह जानने का कोई तरीका नहीं है कि ये परिणाम सुसंगत या मान्य हैं। भगवान जानता है कि मैंने कितने बेंचमार्क बनाए हैं जो अलग-अलग परिणामों की रिपोर्ट करते रहते हैं, विशेष रूप से पुनरावृत्ति क्रम, आकार और क्या नहीं पर निर्भर करता है।
एमएम

6

मुझे लगता है कि मुझे अपनी टिप्पणी को थोड़ा विस्तार देने की आवश्यकता है ...

प्रतिमान शैली के बारे में

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

हालांकि, मैं यह कहूंगा कि Iterable.forEach का उपयोग करने वाला पुनरावृत्ति FP से प्रेरित है और इसके बजाय Java में अधिक FP लाने का परिणाम है (विडंबना यह है कि मैं कहूंगा कि शुद्ध FP में forEach के लिए कोई बहुत अधिक उपयोग नहीं है, क्योंकि यह शुरू करने के अलावा कुछ भी नहीं है। दुष्प्रभाव)।

अंत में मैं यह कहूंगा कि यह स्वाद की शैली का मामला है। आप वर्तमान में लिख रहे हैं।

समानता के बारे में।

प्रदर्शन के दृष्टिकोण से Iterable.forEach foreach (...) के उपयोग से कोई उल्लेखनीय लाभ नहीं है।

Iterable.forEach पर आधिकारिक डॉक्स के अनुसार :

Iterable की सामग्री पर दी गई कार्रवाई करता है, क्रम में तत्वों में पुनरावृत्ति तब होती है , जब तक कि सभी तत्वों को संसाधित नहीं किया जाता है या कार्रवाई अपवाद नहीं फेंकती है।

... यानी डॉक्स बहुत स्पष्ट है कि कोई अंतर्निहित समानता नहीं होगी। एक जोड़ना एलएसपी उल्लंघन होगा।

अब, "समानता संग्रह" हैं जो जावा 8 में वादा किए गए हैं, लेकिन उन लोगों के साथ काम करने के लिए जिन्हें आपको मेरे लिए और अधिक स्पष्ट करने की आवश्यकता है और उनका उपयोग करने के लिए कुछ अतिरिक्त देखभाल करें (उदाहरण के लिए mschenk74 का उत्तर देखें)।

BTW: इस मामले में Stream.forEach का उपयोग किया जाएगा, और यह गारंटी नहीं देता है कि वास्तविक कार्य समानता में किया जाएगा (अंतर्निहित संग्रह पर निर्भर करता है)।

अद्यतन: यह स्पष्ट नहीं है और एक नज़र में थोड़ा बढ़ाया जा सकता है लेकिन शैली और पठनीयता परिप्रेक्ष्य का एक और पहलू है।

सबसे पहले - सादे पुराने फ़ोरलॉप्स सादे और पुराने हैं। हर कोई उन्हें पहले से ही जानता है।

दूसरा, और अधिक महत्वपूर्ण - आप शायद Iterable.forEach का उपयोग केवल एक-लाइनर लैम्ब्डा के साथ करना चाहते हैं। यदि "शरीर" भारी हो जाता है - तो वे पठनीय नहीं होते हैं। आपके पास यहां से 2 विकल्प हैं - आंतरिक कक्षाएं (yuck) का उपयोग करें या सादे पुराने forloop का उपयोग करें। लोग अक्सर परेशान हो जाते हैं जब वे एक ही चीजों को देखते हैं (संग्रह पर पुनरावृत्त) एक ही कोडबेस में विभिन्न vays / शैलियों को किया जा रहा है, और यह मामला प्रतीत होता है।

फिर, यह एक मुद्दा हो सकता है या नहीं हो सकता है। कोड पर काम करने वाले लोगों पर निर्भर करता है।


1
समानांतरवाद को नए "समानांतर संग्रह" की आवश्यकता नहीं है। यह सिर्फ इस बात पर निर्भर करता है कि आपने एक अनुक्रमिक स्ट्रीम (संग्रह का उपयोग कर रहा है।) (या) एक समानांतर के लिए (संग्रह का उपयोग कर।
जेबी निज़ेट

@JBNizet डॉक्स कलेक्शन के अनुसार.परेलरस्ट्रीम () इस बात की गारंटी नहीं देता है कि कलेक्शन लागू करने से पैरेलल स्ट्रीम वापस आ जाएगी। मैं, वास्तव में खुद को आश्चर्यचकित कर रहा हूं, जब ऐसा हो सकता है, लेकिन, शायद यह संग्रह पर निर्भर करता है।
यूजीन लोय

माना। यह संग्रह पर भी निर्भर करता है। लेकिन मेरा कहना था कि सभी मानक संग्रह (ArrayList, इत्यादि) के साथ समानांतर foreach loops पहले से ही उपलब्ध थे। "समानांतर संग्रह" की प्रतीक्षा करने की आवश्यकता नहीं है।
जेबी निज़ेट

@JBNizet आपकी बात पर सहमत हैं, लेकिन यह वास्तव में मेरे लिए "समानांतर संग्रह" का मतलब नहीं है। मैं Collection.parallelStream () को संदर्भित करता हूं जो जावा 8 में "समानांतर संग्रह" के रूप में स्काला की अवधारणा के सादृश्य द्वारा जोड़ा गया था जो बहुत अधिक समान है। इसके अलावा, यह सुनिश्चित नहीं है कि जेएसआर के बिट में इसे कैसे कहा जाता है मैंने कुछ कागजात देखे जो इस जावा 8 सुविधा के लिए समान शब्दावली का उपयोग करते हैं।
यूजीन लोय

1
अंतिम पैराग्राफ के लिए आप एक फ़ंक्शन संदर्भ का उपयोग कर सकते हैं:collection.forEach(MyClass::loopBody);
शाफ़्ट सनकी

6

सबसे उत्कर्ष कार्यात्मक forEachसीमाओं में से एक जाँच अपवाद समर्थन का अभाव है।

एक संभावित समाधान, टर्मिनल को बदलने के लिए है forEachसादे पुराने foreach पाश के साथ:

    Stream<String> stream = Stream.of("", "1", "2", "3").filter(s -> !s.isEmpty());
    Iterable<String> iterable = stream::iterator;
    for (String s : iterable) {
        fileWriter.append(s);
    }

लैम्बदास और धाराओं के भीतर चेक किए गए अपवाद हैंडलिंग पर अन्य वर्कअराउंड के साथ सबसे लोकप्रिय प्रश्नों की सूची यहां दी गई है:

जावा 8 लैम्ब्डा फ़ंक्शन जो अपवाद फेंकता है?

जावा 8: लैम्ब्डा-स्ट्रीम्स, एक्सेप्शन के साथ विधि द्वारा फ़िल्टर

मैं Java 8 स्ट्रीम के अंदर से CHECKED अपवाद कैसे फेंक सकता हूं?

जावा 8: लैम्बडा एक्सप्रेशंस में हैंडलिंग को अनिवार्य चेक किया गया। क्यों अनिवार्य है, वैकल्पिक नहीं?


3

लूप के लिए 1.7 संवर्धित से अधिक जावा 1.8 फॉरएच विधि का लाभ यह है कि कोड लिखते समय आप केवल व्यावसायिक तर्क पर ध्यान केंद्रित कर सकते हैं।

forEach मेथड java.util.function.Consumer ऑब्जेक्ट को एक तर्क के रूप में लेता है, इसलिए यह हमारे व्यापार तर्क को एक अलग स्थान पर रखने में मदद करता है कि आप इसे कभी भी पुन: उपयोग कर सकते हैं।

नीचे स्निपेट पर देखें,

  • यहाँ मैंने नई क्लास बनाई है जो कन्ज्यूमर क्लास से क्लास के तरीके को स्वीकार कर लेगी, जहाँ आप अतिरिक्त कार्यक्षमता जोड़ सकते हैं, Iterter से अधिक .. !!!!!!

    class MyConsumer implements Consumer<Integer>{
    
        @Override
        public void accept(Integer o) {
            System.out.println("Here you can also add your business logic that will work with Iteration and you can reuse it."+o);
        }
    }
    
    public class ForEachConsumer {
    
        public static void main(String[] args) {
    
            // Creating simple ArrayList.
            ArrayList<Integer> aList = new ArrayList<>();
            for(int i=1;i<=10;i++) aList.add(i);
    
            //Calling forEach with customized Iterator.
            MyConsumer consumer = new MyConsumer();
            aList.forEach(consumer);
    
    
            // Using Lambda Expression for Consumer. (Functional Interface) 
            Consumer<Integer> lambda = (Integer o) ->{
                System.out.println("Using Lambda Expression to iterate and do something else(BI).. "+o);
            };
            aList.forEach(lambda);
    
            // Using Anonymous Inner Class.
            aList.forEach(new Consumer<Integer>(){
                @Override
                public void accept(Integer o) {
                    System.out.println("Calling with Anonymous Inner Class "+o);
                }
            });
        }
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.