java - जावा 8 Iterable.forEach() बनाम foreach पाश




for-loop java-8 (6)

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

जावा 8:

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

जावा 7:

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

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

संपादित करें

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


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

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

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

  • यहां मैंने नई कक्षा बनाई है जो उपभोक्ता वर्ग से स्वीकृति कक्षा विधि को ओवरराइड कर देगी, जहां आप अतिरिक्त कार्यक्षमता जोड़ सकते हैं, इटरेशन से अधिक .. !!!!!!

    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);
                }
            });
        }
    }
    

इस प्रश्न को पढ़ने पर कोई इंप्रेशन प्राप्त कर सकता है, कि Iterable#forEach अभिव्यक्तियों के साथ संयोजन में 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());
    

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

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

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

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

    @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++;
                }
            }
        }
    }
    

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

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

  • समानांतर निष्पादन के लिए वैकल्पिक समर्थन प्रदान करने के लिए: यह पहले से ही कहा जा चुका है कि streams का उपयोग करके समानांतर में Iterable#forEach के कार्यात्मक इंटरफ़ेस को निष्पादित करने की संभावना निश्चित रूप से एक महत्वपूर्ण पहलू है। चूंकि Collection#parallelStream() गारंटी नहीं देता है, कि लूप वास्तव में समानांतर में निष्पादित किया जाता है, इसलिए इसे एक वैकल्पिक सुविधा पर विचार करना चाहिए। सूची के साथ अपनी सूची में list.parallelStream().forEach(...); करके 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();
            }
        }
    }
    

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


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

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

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

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

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

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

प्रदर्शन बिंदु से Iterable.forEach foreach (...) पर उपयोग करने से कोई वादा किया गया लाभ नहीं है।

Iterable.forEach पर आधिकारिक दस्तावेज़ों के मुताबिक:

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

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

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

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

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

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

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

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


लाभ तब होता है जब संचालन समानांतर में निष्पादित किया जा सकता है। ( 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 लागू होता है। इंटरफ़ेस इंटरफ़ेस यह अब सत्य नहीं है।


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: लैम्ब्डा-स्ट्रीम, अपवाद के साथ विधि द्वारा फ़िल्टर करें

मैं जावा 8 धाराओं के अंदर से चेक अपवाद कैसे फेंक सकता हूं?

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


टीएल; डीआर : List.stream().forEach() सबसे तेज़ था।

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

  1. क्लासिक के for
  2. क्लासिक foreach
  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() का उपयोग कर 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);

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

क्लासिक के for

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

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

क्लासिक foreach

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

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

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 एमएस





java-8