जावा 8 - सूची बदलने के लिए सबसे अच्छा तरीका: नक्शा या फ़ॉरच?


188

मेरे पास एक सूची है myListToParseजहां मैं तत्वों को फ़िल्टर करना चाहता हूं और प्रत्येक तत्व पर एक विधि लागू करना चाहता हूं, और परिणाम को दूसरी सूची में जोड़ना चाहिए myFinalList

जावा 8 के साथ मैंने देखा कि मैं इसे 2 अलग-अलग तरीकों से कर सकता हूं। मैं उनके बीच अधिक कुशल तरीका जानना चाहता हूं और यह समझना चाहता हूं कि एक तरीका दूसरे से बेहतर क्यों है।

मैं तीसरे तरीके के बारे में किसी भी सुझाव के लिए खुला हूं।

विधि 1:

myFinalList = new ArrayList<>();
myListToParse.stream()
        .filter(elt -> elt != null)
        .forEach(elt -> myFinalList.add(doSomething(elt)));

विधि 2:

myFinalList = myListToParse.stream()
        .filter(elt -> elt != null)
        .map(elt -> doSomething(elt))
        .collect(Collectors.toList()); 

55
दूसरा एक। एक उचित कार्य का कोई दुष्प्रभाव नहीं होना चाहिए, आपके पहले कार्यान्वयन में आप बाहरी दुनिया को संशोधित कर रहे हैं।
धन्यवादForAllTheFish

37
शैली का मामला है, लेकिन elt -> elt != nullसाथ बदला जा सकताObjects::nonNull
the8472

2
@ the8472 यह सुनिश्चित करने के लिए भी बेहतर होगा कि पहली बार में संग्रह में कोई अशक्त मूल्य नहीं हैं, और Optional<T>इसके साथ संयोजन में उपयोग करें flatMap
हरमन

2
@SzymonRoziewski, काफी नहीं। इस के रूप में तुच्छ के लिए कुछ के लिए, इस निर्माण म्यूट का उपयोग करके हुड के तहत समानांतर धारा को सेटअप करने के लिए आवश्यक कार्य होगा।
एमके

2
ध्यान दें कि आप .map(this::doSomething)मान सकते हैं कि doSomethingयह एक गैर-स्थैतिक विधि है। यदि यह स्थिर है तो आप thisवर्ग नाम से बदल सकते हैं ।
हरमन

जवाबों:


153

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

विधि 2 बेहतर है क्योंकि

  1. यह एक संग्रह को बदलने की आवश्यकता नहीं है जो लंबोदर अभिव्यक्ति के बाहर मौजूद है,

  2. यह अधिक पठनीय है क्योंकि संग्रह पाइपलाइन में किए गए विभिन्न चरणों को क्रमिक रूप से लिखा गया है: पहले एक फिल्टर ऑपरेशन, फिर एक मानचित्र संचालन, फिर परिणाम एकत्र करना (संग्रह पाइपलाइनों के लाभों के बारे में अधिक जानकारी के लिए, मार्टिन फाउलर का उत्कृष्ट लेख देखें ),

  3. आप आसानी से जिस तरह Collectorसे इस्तेमाल किया जाता है , उसे बदलकर मूल्यों को एकत्र किया जा सकता है। कुछ मामलों में आपको अपना खुद का लिखने की आवश्यकता हो सकती है Collector, लेकिन फिर लाभ यह है कि आप आसानी से इसका पुन: उपयोग कर सकते हैं।


43

मैं मौजूदा जवाबों से सहमत हूं कि दूसरा रूप बेहतर है क्योंकि इसका कोई साइड इफेक्ट नहीं है और इसे समानांतर करना आसान है (बस एक समानांतर उपयोग करें)।

प्रदर्शन के अनुसार, ऐसा प्रतीत होता है कि वे तब तक बराबर हैं जब तक आप समानांतर धाराओं का उपयोग शुरू नहीं करते। उस स्थिति में, मानचित्र वास्तव में बहुत बेहतर प्रदर्शन करेगा। माइक्रो बेंचमार्क परिणामों के नीचे देखें :

Benchmark                         Mode  Samples    Score   Error  Units
SO28319064.forEach                avgt      100  187.310 ± 1.768  ms/op
SO28319064.map                    avgt      100  189.180 ± 1.692  ms/op
SO28319064.mapWithParallelStream  avgt      100   55,577 ± 0,782  ms/op

आप पहले उदाहरण को उसी तरीके से बढ़ावा नहीं दे सकते क्योंकि forEach एक टर्मिनल विधि है - यह शून्य देता है - इसलिए आपको एक स्टेटफुल लैम्ब्डा का उपयोग करने के लिए मजबूर किया जाता है। लेकिन यह वास्तव में एक बुरा विचार है यदि आप समानांतर धाराओं का उपयोग कर रहे हैं

अंत में ध्यान दें कि आपके दूसरे स्निपेट को विधि संदर्भों और स्थैतिक आयातों के साथ अधिक संक्षिप्त तरीके से लिखा जा सकता है:

myFinalList = myListToParse.stream()
    .filter(Objects::nonNull)
    .map(this::doSomething)
    .collect(toList()); 

1
प्रदर्शन के बारे में, आपके मामले में "मानचित्र" वास्तव में "forEach" पर जीतता है यदि आप समानांतरस्ट्रीम का उपयोग करते हैं। मिलीसेकेंड में मेरे benchmaks: SO28319064.forEach: 187,310 ± 1768 एमएस / सेशन - SO28319064.map: 189,180 ± 1,692 एमएस / सेशन --SO28319064.mapParallelStream: 55,577 ± 0782 एमएस / सेशन
ग्यूसेप बेर्तोने

2
@GiuseppeBertone, यह आत्मसात करने के लिए है, लेकिन मेरे विचार से आपका संपादन मूल लेखक के इरादे का खंडन करता है। यदि आप अपना स्वयं का उत्तर जोड़ना चाहते हैं, तो मौजूदा को संपादित करने के बजाय इसे जोड़ना बेहतर होगा। इसके अलावा अब माइक्रोबेनमार्क का लिंक परिणामों के लिए प्रासंगिक नहीं है।
टैगिर वलेव

5

धाराओं का उपयोग करने का एक मुख्य लाभ यह है कि यह डेटा को एक घोषणात्मक तरीके से, अर्थात् प्रोग्रामिंग की एक कार्यात्मक शैली का उपयोग करने की क्षमता देता है। यह मुफ्त अर्थ के लिए बहु-थ्रेडिंग क्षमता भी देता है, जिससे आपकी धारा समवर्ती बनाने के लिए किसी भी अतिरिक्त मल्टी-थ्रेडेड कोड को लिखने की आवश्यकता नहीं है।

यह मानते हुए कि आप प्रोग्रामिंग की इस शैली की खोज कर रहे हैं, यह है कि आप इन लाभों का फायदा उठाना चाहते हैं, तो आपका पहला कोड नमूना संभवतः तब से कार्यात्मक नहीं है foreach विधि को टर्मिनल के रूप में वर्गीकृत किया गया है (जिसका अर्थ है कि यह दुष्प्रभाव पैदा कर सकता है)।

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

  1. गैर-हस्तक्षेप, जिसका अर्थ है कि फ़ंक्शन को धारा के स्रोत में परिवर्तन नहीं करना चाहिए यदि यह गैर-समवर्ती है (जैसे ArrayList)।
  2. समानांतर प्रसंस्करण करते समय अनपेक्षित परिणामों से बचने के लिए स्टेटलेस (थ्रेड शेड्यूलिंग अंतर के कारण)।

दूसरे दृष्टिकोण के साथ एक और लाभ यह है कि यदि धारा समानांतर है और कलेक्टर समवर्ती और अव्यवस्थित है तो ये विशेषताएं एकत्रित करने के लिए कटौती ऑपरेशन के लिए उपयोगी संकेत प्रदान कर सकती हैं।


4

यदि आप ग्रहण संग्रह का उपयोग करते हैं तो आप collectIf()विधि का उपयोग कर सकते हैं ।

MutableList<Integer> source =
    Lists.mutable.with(1, null, 2, null, 3, null, 4, null, 5);

MutableList<String> result = source.collectIf(Objects::nonNull, String::valueOf);

Assert.assertEquals(Lists.immutable.with("1", "2", "3", "4", "5"), result);

यह उत्सुकता से मूल्यांकन करता है और स्ट्रीम का उपयोग करने की तुलना में थोड़ा तेज होना चाहिए।

नोट: मैं एक्लिप्स कलेक्शंस के लिए कमिट हूं।


1

मैं दूसरा तरीका पसंद करता हूं।

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

जब आप उपयोग करते हैं toList, तो धारा एपीआई एक समानांतर धारा का उपयोग करते हुए भी आदेश को संरक्षित करेगा।


मुझे यकीन नहीं है कि यह सही सलाह है: वह forEachOrderedइसके बजाय उपयोग कर सकता है forEachयदि वह एक समानांतर धारा का उपयोग करना चाहता है लेकिन फिर भी आदेश को संरक्षित करता है। लेकिन forEachराज्यों के लिए दस्तावेज के रूप में , एनकाउंटर ऑर्डर को संरक्षित करने से समानता का लाभ मिलता है। मुझे संदेह है कि toListतब के मामले में भी यही है ।
हरमन

0

एक तीसरा विकल्प है - का उपयोग करते हुए stream().toArray()टिप्पणियों को देखें कि स्ट्रीम के पास एक लोलक विधि क्यों नहीं थी । यह फॉरएच () या कलेक्ट (), और कम अभिव्यंजक की तुलना में धीमा हो जाता है। इसे बाद में JDK बिल्ड में ऑप्टिमाइज़ किया जा सकता है, इसलिए इसे केवल मामले में यहाँ जोड़ रहा है।

यह सोचते हैं List<String>

    myFinalList = Arrays.asList(
            myListToParse.stream()
                    .filter(Objects::nonNull)
                    .map(this::doSomething)
                    .toArray(String[]::new)
    );

माइक्रो-माइक्रो बेंचमार्क के साथ, 1M प्रविष्टियां, 20% नल और सरल परिवर्तन doSomething () में

private LongSummaryStatistics benchmark(final String testName, final Runnable methodToTest, int samples) {
    long[] timing = new long[samples];
    for (int i = 0; i < samples; i++) {
        long start = System.currentTimeMillis();
        methodToTest.run();
        timing[i] = System.currentTimeMillis() - start;
    }
    final LongSummaryStatistics stats = Arrays.stream(timing).summaryStatistics();
    System.out.println(testName + ": " + stats);
    return stats;
}

परिणाम हैं

समानांतर:

toArray: LongSummaryStatistics{count=10, sum=3721, min=321, average=372,100000, max=535}
forEach: LongSummaryStatistics{count=10, sum=3502, min=249, average=350,200000, max=389}
collect: LongSummaryStatistics{count=10, sum=3325, min=265, average=332,500000, max=368}

अनुक्रमिक:

toArray: LongSummaryStatistics{count=10, sum=5493, min=517, average=549,300000, max=569}
forEach: LongSummaryStatistics{count=10, sum=5316, min=427, average=531,600000, max=571}
collect: LongSummaryStatistics{count=10, sum=5380, min=444, average=538,000000, max=557}

नल और फिल्टर के बिना समानांतर (इसलिए स्ट्रीम है SIZED): इस तरह के मामले में torrays का सबसे अच्छा प्रदर्शन है, और .forEach()रसीद ArrayList पर "indexOutOfBounds" के साथ विफल रहता है, के साथ प्रतिस्थापित करना था.forEachOrdered()

toArray: LongSummaryStatistics{count=100, sum=75566, min=707, average=755,660000, max=1107}
forEach: LongSummaryStatistics{count=100, sum=115802, min=992, average=1158,020000, max=1254}
collect: LongSummaryStatistics{count=100, sum=88415, min=732, average=884,150000, max=1014}

0

हो सकता है विधि 3।

मैं हमेशा तर्क को अलग रखना पसंद करता हूं।

Predicate<Long> greaterThan100 = new Predicate<Long>() {
            @Override
            public boolean test(Long currentParameter) {
                return currentParameter > 100;
            }
        };

        List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
        List<Long> resultList = sourceLongList.parallelStream().filter(greaterThan100).collect(Collectors.toList());

0

यदि 3 Pary Libaries का उपयोग करना ठीक है तो साइक्लोप्स-रिएक्शन में निर्मित इस कार्यक्षमता के साथ Lazy विस्तारित संग्रह को परिभाषित करता है। उदाहरण के लिए हम इसे लिख सकते हैं।

लिस्टएक्स myListToParse;

ListX myFinalList = myListToParse.filter (elt -> elt - null) .map (elt -> doSomething (elt));

myFinalList का मूल्यांकन तब तक नहीं किया जाता है जब तक कि पहली पहुंच (और भौतिकीकृत सूची के बाद कैश और पुन: उपयोग नहीं किया जाता है)।

[प्रकटीकरण मैं चक्रवात-प्रतिक्रिया का प्रमुख विकासकर्ता हूं]

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