Java8 स्ट्रीम के तत्वों को मौजूदा सूची में कैसे जोड़ा जाए


155

कलेक्टर का जावाडॉक दिखाता है कि एक स्ट्रीम के तत्वों को एक नई सूची में कैसे एकत्र किया जाए। क्या कोई एक-लाइनर है जो परिणामों को मौजूदा ArrayList में जोड़ता है?


1
यहाँ पहले से ही एक उत्तर है । आइटम के लिए देखो "एक मौजूदा में जोड़ें Collection"
Holger

जवाबों:


197

नोट: nosid का उत्तर दिखाता है कि किसी मौजूदा संग्रह का उपयोग करके कैसे जोड़ा जाए forEachOrdered()। यह मौजूदा संग्रह को बदलने के लिए एक उपयोगी और प्रभावी तकनीक है। मेरा जवाब पता है कि आपको Collectorकिसी मौजूदा संग्रह को बदलने के लिए उपयोग क्यों नहीं करना चाहिए ।

संक्षिप्त उत्तर नहीं है , कम से कम, सामान्य रूप से नहीं, आपको Collectorकिसी मौजूदा संग्रह को संशोधित करने के लिए उपयोग नहीं करना चाहिए ।

कारण यह है कि संग्रहकर्ताओं को समानांतरवाद का समर्थन करने के लिए डिज़ाइन किया गया है, यहां तक ​​कि उन संग्रह पर भी जो थ्रेड-सुरक्षित नहीं हैं। जिस तरह से वे ऐसा करते हैं कि प्रत्येक थ्रेड स्वतंत्र रूप से मध्यवर्ती परिणामों के अपने संग्रह पर संचालित होता है। जिस तरह से प्रत्येक थ्रेड को अपना संग्रह मिलता है, उसे कॉल करना Collector.supplier()होता है जिसे हर बार एक नया संग्रह वापस करना होता है ।

मध्यवर्ती परिणामों के इन संग्रहों को फिर से मिला दिया जाता है, फिर से एक थ्रेड-सीमित फैशन में, जब तक कि एक एकल परिणाम संग्रह न हो। यह collect()ऑपरेशन का अंतिम परिणाम है ।

Balder और assylias के एक जोड़े के जवाब ने Collectors.toCollection()एक आपूर्तिकर्ता का उपयोग करने और फिर पास करने का सुझाव दिया है जो एक नई सूची के बजाय एक मौजूदा सूची देता है। यह आपूर्तिकर्ता पर आवश्यकता का उल्लंघन करता है, जो यह है कि यह हर बार एक नया, खाली संग्रह लौटाता है।

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

आइए एक सरल उदाहरण लेते हैं:

List<String> destList = new ArrayList<>(Arrays.asList("foo"));
List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5");
newList.parallelStream()
       .collect(Collectors.toCollection(() -> destList));
System.out.println(destList);

जब मैं इस कार्यक्रम को चलाता हूं, तो मुझे अक्सर ए ArrayIndexOutOfBoundsException। ऐसा इसलिए है क्योंकि कई थ्रेड्स ArrayListएक थ्रेड-असुरक्षित डेटा संरचना पर काम कर रहे हैं । ठीक है, चलो इसे सिंक्रनाइज़ करें:

List<String> destList =
    Collections.synchronizedList(new ArrayList<>(Arrays.asList("foo")));

यह अब एक अपवाद के साथ विफल नहीं होगा। लेकिन अपेक्षित परिणाम के बजाय:

[foo, 0, 1, 2, 3]

यह इस तरह अजीब परिणाम देता है:

[foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0]

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

फिर, जब इन मध्यवर्ती संग्रहों को मिला दिया जाता है, तो यह मूल रूप से सूची को स्वयं में विलय कर देता है। सूची का उपयोग करके विलय कर दिया जाता है List.addAll(), जो कहता है कि यदि ऑपरेशन के दौरान स्रोत संग्रह को संशोधित किया जाता है तो परिणाम अपरिभाषित होते हैं। इस मामले में, ArrayList.addAll()एक सरणी-कॉपी ऑपरेशन करता है, इसलिए यह खुद को डुप्लिकेट करता है, जो कि जिस तरह की उम्मीद करता है, मुझे लगता है। (ध्यान दें कि अन्य सूची कार्यान्वयन में पूरी तरह से अलग व्यवहार हो सकता है।) वैसे भी, यह गंतव्य में अजीब परिणाम और डुप्लिकेट किए गए तत्वों की व्याख्या करता है।

आप कह सकते हैं, "मैं बस अपनी स्ट्रीम को क्रमिक रूप से चलाने के लिए सुनिश्चित करूंगा" और आगे बढ़ो और इस तरह कोड लिखें

stream.collect(Collectors.toCollection(() -> existingList))

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

ठीक है, तो sequential()इस कोड का उपयोग करने से पहले किसी भी स्ट्रीम पर कॉल करना न भूलें।

stream.sequential().collect(Collectors.toCollection(() -> existingList))

बेशक, आपको हर बार ऐसा करना याद होगा, है ना? :-) आइए आपको बताते हैं। फिर, प्रदर्शन टीम सोच रही होगी कि उनके सभी सावधानीपूर्वक बनाए गए समानांतर कार्यान्वयन किसी भी स्पीडअप को क्यों नहीं प्रदान कर रहे हैं। और एक बार फिर वे इसे आपके कोड तक ट्रेस कर देंगे जो पूरी धारा को क्रमिक रूप से चलाने के लिए मजबूर कर रहा है।

यह मत करो।


महान व्याख्या! - इसे स्पष्ट करने के लिए धन्यवाद। मैं अपने उत्तर को संभव समानांतर धाराओं के साथ कभी नहीं करने की सलाह देता हूं।
बाल्डर

3
यदि प्रश्न है, अगर किसी स्ट्रीम के तत्वों को मौजूदा सूची में जोड़ने के लिए एक-लाइनर है, तो संक्षिप्त उत्तर हां है । मेरा जवाब देखिए। हालाँकि, मैं आपसे सहमत हूँ, कि किसी मौजूदा सूची के संयोजन में कलेक्टरों। कॉटलेक्शन () का उपयोग करना गलत तरीका है।
nosid

सच। मुझे लगता है कि हम सभी कलेक्टरों के बारे में सोच रहे थे।
स्टुअर्ट मार्क्स

बहुत बढ़िया जवाब! मैं अनुक्रमिक समाधान का उपयोग करने के लिए बहुत लुभाता हूं भले ही आप स्पष्ट रूप से सलाह दें क्योंकि कहा गया है कि इसे अच्छी तरह से काम करना चाहिए। लेकिन तथ्य यह है कि javadoc के लिए आवश्यक है कि toCollectionविधि का आपूर्तिकर्ता तर्क हर बार एक नया और खाली संग्रह लौटाए जो मुझे नहीं समझाता है। मैं वास्तव में कोर जावा कक्षाओं के जावाडॉक अनुबंध को तोड़ना चाहता हूं।
ज़ूम करें

1
@AlexCurvers यदि आप चाहते हैं कि धारा का दुष्प्रभाव हो, तो आप लगभग निश्चित रूप से उपयोग करना चाहते हैं forEachOrdered। साइड इफेक्ट्स में मौजूदा संग्रह में तत्व जोड़ना शामिल है, भले ही उसमें पहले से ही तत्व हों। यदि आप चाहते हैं एक धारा के तत्वों एक में रखा गया नया संग्रह, उपयोग collect(Collectors.toList())या toSet()या toCollection()
स्टुअर्ट मार्क्स

169

जहाँ तक मैं देख सकता हूँ, अन्य सभी उत्तर अब तक एक कलेक्टर का उपयोग करके एक मौजूदा स्ट्रीम में तत्व जोड़ते हैं। हालाँकि, एक छोटा समाधान है, और यह अनुक्रमिक और समानांतर धाराओं दोनों के लिए काम करता है। आप बस विधि संदर्भ के साथ संयोजन में forEachOrdered विधि का उपयोग कर सकते हैं ।

List<String> source = ...;
List<Integer> target = ...;

source.stream()
      .map(String::length)
      .forEachOrdered(target::add);

केवल प्रतिबंध है, वह स्रोत और लक्ष्य अलग-अलग सूचियाँ हैं, क्योंकि जब तक यह संसाधित होता है तब तक आपको किसी स्रोत के स्रोत में परिवर्तन करने की अनुमति नहीं है।

ध्यान दें कि यह समाधान अनुक्रमिक और समानांतर धाराओं दोनों के लिए काम करता है। हालाँकि, यह कंसिस्टेंसी से लाभ नहीं करता है। विधि संदर्भ जिसे forEachOrdered में दिया गया है , हमेशा क्रमिक रूप से निष्पादित किया जाएगा।


6
+1 यह मजाकिया है कि कितने लोग दावा करते हैं कि जब एक होता है तो कोई संभावना नहीं होती है। Btw। मैंने दो महीने पहलेforEach(existing::add) एक उत्तर में एक संभावना के रूप में शामिल किया । मुझे भी जोड़ा जाना चाहिए forEachOrdered...
Holger

5
क्या कोई कारण है जो आप के forEachOrderedबजाय इस्तेमाल किया है forEach?
मेसाउंड

6
@ मेम्बर्साउंड: अनुक्रमिक और समानांतर धाराओं forEachOrderedदोनों के लिए काम करता है। इसके विपरीत, समानांतर धाराओं के लिए पारित समारोह वस्तु को समवर्ती रूप से निष्पादित कर सकता है। इस स्थिति में, फ़ंक्शन ऑब्जेक्ट को ठीक से सिंक्रनाइज़ किया जाना चाहिए, जैसे कि ए का उपयोग करके । forEachVector<Integer>
nosid

@BrianGoetz: मुझे यह स्वीकार करना होगा, कि Stream.forEachOrdered का दस्तावेज़ीकरण थोड़ा कठिन है। हालाँकि, मैं इस विनिर्देश की कोई उचित व्याख्या नहीं देख सकता , जिसमें कोई भी कॉल-टू - रिलेशनशिप नहीं हैtarget::add । भले ही जिस विधि से थ्रेड को लागू किया जाए, कोई डेटा रेस नहीं है । मुझे उम्मीद है कि आपको पता चल जाएगा।
nosid

यह सबसे उपयोगी उत्तर है, जहां तक ​​मेरा सवाल है। यह वास्तव में एक धारा से एक मौजूदा सूची में आइटम सम्मिलित करने का एक व्यावहारिक तरीका दिखाता है, जो कि वह प्रश्न है जो (भ्रामक शब्द "संग्रह" के बावजूद) के लिए पूछा गया है
घरघराहट

12

संक्षिप्त उत्तर नहीं है (या नहीं होना चाहिए)। संपादित करें: हाँ, यह संभव है (नीचे देखें 'एस जवाब'), लेकिन पढ़ते रहें। EDIT2: लेकिन स्टुअर्ट मार्क्स के उत्तर को अभी तक एक और कारण के लिए देखें कि आपको अभी भी ऐसा क्यों नहीं करना चाहिए!

लंबा जवाब:

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

यदि आपको पुरानी सूची को संशोधित करना है , तो केवल मैप की गई वस्तुओं को एक नई सूची में एकत्र करें:

final List<Integer> newList = list.stream()
                                  .filter(n -> n % 2 == 0)
                                  .collect(Collectors.toList());

और फिर list.addAll(newList)- फिर से: यदि आप वास्तव में करना चाहिए।

(या पुराने और नए को समेटते हुए एक नई सूची का निर्माण करें, और इसे वापस listचर पर असाइन करें - यह एफपी की भावना से थोड़ा अधिक है addAll)

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

वास्तव में लंबा उत्तर: (यदि आप वास्तव में एफपी इंट्रो / पुस्तक को खोजने और पढ़ने का प्रयास शामिल हैं, जैसा कि सुझाव दिया गया है)

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


@assylias: तार्किक रूप से, यह गलत नहीं था क्योंकि वहाँ या हिस्सा था; फिर भी, एक नोट जोड़ा।
एरिक कपलुन

1
संक्षिप्त उत्तर सही है। प्रस्तावित एक-लाइनर सरल मामलों में सफल होगा लेकिन सामान्य मामले में विफल।
स्टुअर्ट मार्क्स

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

@StuartMarks: दिलचस्प: किन मामलों में एसिलियास के उत्तर में दिए गए समाधान टूट जाते हैं? (और समानता के बारे में अच्छे अंक — मुझे लगता है कि मैं एफपी की वकालत करने के लिए बहुत उत्सुक था)
एरिक कपलुन

@ErikAllik मैंने एक उत्तर जोड़ा है जो इस मुद्दे को कवर करता है।
स्टुअर्ट मार्क्स

11

एरिक अल्लिक ने पहले से ही बहुत अच्छे कारण दिए हैं, आप एक स्ट्रीम के तत्वों को मौजूदा सूची में क्यों नहीं एकत्र करना चाहते हैं।

वैसे भी, आप निम्नलिखित एक-लाइनर का उपयोग कर सकते हैं, अगर आपको वास्तव में इस कार्यक्षमता की आवश्यकता है।

लेकिन जैसा कि स्टुअर्ट मार्क्स अपने जवाब में बताते हैं, आपको ऐसा कभी नहीं करना चाहिए , अगर धाराएं समानांतर धाराएं हो सकती हैं - अपने जोखिम पर उपयोग करें ...

list.stream().collect(Collectors.toCollection(() -> myExistingList));

आह, यह शर्म की बात है: पी
एरिक कपलुन

2
यदि धारा समान्तर चलती है तो यह तकनीक बुरी तरह से विफल हो जाएगी।
स्टुअर्ट मार्क्स

1
यह सुनिश्चित करने के लिए संग्रह प्रदाता की जिम्मेदारी होगी कि वह विफल न हो - जैसे समवर्ती संग्रह प्रदान करके।
Balder

2
नहीं, यह कोड toCollection () की आवश्यकता का उल्लंघन करता है, जो यह है कि आपूर्तिकर्ता उचित प्रकार का एक नया, खाली संग्रह लौटाता है। भले ही गंतव्य थ्रेड-सुरक्षित है, लेकिन समानांतर मामले के लिए किया गया विलय गलत परिणाम देगा।
स्टुअर्ट मार्क्स

1
@Balder मैंने एक उत्तर जोड़ा है जो इसे स्पष्ट करना चाहिए।
स्टुअर्ट मार्क्स

4

आपको बस अपनी मूल सूची का उल्लेख करना होगा जो कि Collectors.toList()रिटर्न है।

यहाँ एक डेमो है:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Reference {

  public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    System.out.println(list);

    // Just collect even numbers and start referring the new list as the original one.
    list = list.stream()
               .filter(n -> n % 2 == 0)
               .collect(Collectors.toList());
    System.out.println(list);
  }
}

और यहां बताया गया है कि कैसे आप अपनी मूल सूची में नए बनाए गए तत्वों को केवल एक पंक्ति में जोड़ सकते हैं।

List<Integer> list = ...;
// add even numbers from the list to the list again.
list.addAll(list.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList())
);

यही कार्य कार्यात्मक प्रोग्रामिंग प्रतिमान प्रदान करता है।


मेरा कहने का मतलब था कि मौजूदा सूची में जोड़ना / इकट्ठा करना न केवल पुनर्मूल्यांकन है।
कोडफेक्स

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


0

मैं पुरानी सूची और नई सूची को धाराओं के रूप में संक्षिप्त करूंगा और परिणामों को गंतव्य सूची में सहेजूंगा। समानांतर में भी अच्छा काम करता है।

मैं स्टुअर्ट मार्क्स द्वारा दिए गए स्वीकृत उत्तर के उदाहरण का उपयोग करूंगा:

List<String> destList = Arrays.asList("foo");
List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5");

destList = Stream.concat(destList.stream(), newList.stream()).parallel()
            .collect(Collectors.toList());
System.out.println(destList);

//output: [foo, 0, 1, 2, 3, 4, 5]

आशा करता हूँ की ये काम करेगा।

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