कलेक्टर का जावाडॉक दिखाता है कि एक स्ट्रीम के तत्वों को एक नई सूची में कैसे एकत्र किया जाए। क्या कोई एक-लाइनर है जो परिणामों को मौजूदा ArrayList में जोड़ता है?
कलेक्टर का जावाडॉक दिखाता है कि एक स्ट्रीम के तत्वों को एक नई सूची में कैसे एकत्र किया जाए। क्या कोई एक-लाइनर है जो परिणामों को मौजूदा ArrayList में जोड़ता है?
जवाबों:
नोट: 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))
बेशक, आपको हर बार ऐसा करना याद होगा, है ना? :-) आइए आपको बताते हैं। फिर, प्रदर्शन टीम सोच रही होगी कि उनके सभी सावधानीपूर्वक बनाए गए समानांतर कार्यान्वयन किसी भी स्पीडअप को क्यों नहीं प्रदान कर रहे हैं। और एक बार फिर वे इसे आपके कोड तक ट्रेस कर देंगे जो पूरी धारा को क्रमिक रूप से चलाने के लिए मजबूर कर रहा है।
यह मत करो।
toCollection
विधि का आपूर्तिकर्ता तर्क हर बार एक नया और खाली संग्रह लौटाए जो मुझे नहीं समझाता है। मैं वास्तव में कोर जावा कक्षाओं के जावाडॉक अनुबंध को तोड़ना चाहता हूं।
forEachOrdered
। साइड इफेक्ट्स में मौजूदा संग्रह में तत्व जोड़ना शामिल है, भले ही उसमें पहले से ही तत्व हों। यदि आप चाहते हैं एक धारा के तत्वों एक में रखा गया नया संग्रह, उपयोग collect(Collectors.toList())
या toSet()
या toCollection()
।
जहाँ तक मैं देख सकता हूँ, अन्य सभी उत्तर अब तक एक कलेक्टर का उपयोग करके एक मौजूदा स्ट्रीम में तत्व जोड़ते हैं। हालाँकि, एक छोटा समाधान है, और यह अनुक्रमिक और समानांतर धाराओं दोनों के लिए काम करता है। आप बस विधि संदर्भ के साथ संयोजन में forEachOrdered विधि का उपयोग कर सकते हैं ।
List<String> source = ...;
List<Integer> target = ...;
source.stream()
.map(String::length)
.forEachOrdered(target::add);
केवल प्रतिबंध है, वह स्रोत और लक्ष्य अलग-अलग सूचियाँ हैं, क्योंकि जब तक यह संसाधित होता है तब तक आपको किसी स्रोत के स्रोत में परिवर्तन करने की अनुमति नहीं है।
ध्यान दें कि यह समाधान अनुक्रमिक और समानांतर धाराओं दोनों के लिए काम करता है। हालाँकि, यह कंसिस्टेंसी से लाभ नहीं करता है। विधि संदर्भ जिसे forEachOrdered में दिया गया है , हमेशा क्रमिक रूप से निष्पादित किया जाएगा।
forEach(existing::add)
एक उत्तर में एक संभावना के रूप में शामिल किया । मुझे भी जोड़ा जाना चाहिए forEachOrdered
...
forEachOrdered
बजाय इस्तेमाल किया है forEach
?
forEachOrdered
दोनों के लिए काम करता है। इसके विपरीत, समानांतर धाराओं के लिए पारित समारोह वस्तु को समवर्ती रूप से निष्पादित कर सकता है। इस स्थिति में, फ़ंक्शन ऑब्जेक्ट को ठीक से सिंक्रनाइज़ किया जाना चाहिए, जैसे कि ए का उपयोग करके । forEach
Vector<Integer>
target::add
। भले ही जिस विधि से थ्रेड को लागू किया जाए, कोई डेटा रेस नहीं है । मुझे उम्मीद है कि आपको पता चल जाएगा।
संक्षिप्त उत्तर नहीं है (या नहीं होना चाहिए)। संपादित करें: हाँ, यह संभव है (नीचे देखें 'एस जवाब'), लेकिन पढ़ते रहें। EDIT2: लेकिन स्टुअर्ट मार्क्स के उत्तर को अभी तक एक और कारण के लिए देखें कि आपको अभी भी ऐसा क्यों नहीं करना चाहिए!
लंबा जवाब:
जावा 8 में इन निर्माणों का उद्देश्य भाषा को कार्यात्मक प्रोग्रामिंग की कुछ अवधारणाओं को पेश करना है; कार्यात्मक प्रोग्रामिंग में, डेटा संरचनाओं को आम तौर पर संशोधित नहीं किया जाता है, इसके बजाय, नए लोगों को मैप्स, फ़िल्टर, फोल्ड / कम और कई अन्य जैसे परिवर्तनों के माध्यम से पुराने से बनाया जाता है।
यदि आपको पुरानी सूची को संशोधित करना है , तो केवल मैप की गई वस्तुओं को एक नई सूची में एकत्र करें:
final List<Integer> newList = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
और फिर list.addAll(newList)
- फिर से: यदि आप वास्तव में करना चाहिए।
(या पुराने और नए को समेटते हुए एक नई सूची का निर्माण करें, और इसे वापस list
चर पर असाइन करें - यह एफपी की भावना से थोड़ा अधिक है addAll
)
एपीआई के रूप में: भले ही एपीआई इसे अनुमति देता है (फिर से, एसिलियस का जवाब देखें) आपको ऐसा करने से बचने की कोशिश करनी चाहिए, भले ही कम से कम, सामान्य रूप से। यह सबसे अच्छा है कि प्रतिमान (एफपी) से न लड़ें और इसे लड़ने के बजाय इसे सीखने की कोशिश करें (भले ही जावा आम तौर पर एफपी भाषा नहीं है), और यदि आवश्यक हो तो केवल "डर्टियर" रणनीति का सहारा लें।
वास्तव में लंबा उत्तर: (यदि आप वास्तव में एफपी इंट्रो / पुस्तक को खोजने और पढ़ने का प्रयास शामिल हैं, जैसा कि सुझाव दिया गया है)
यह पता लगाने के लिए कि मौजूदा सूचियों को संशोधित करना सामान्य रूप से एक बुरा विचार क्यों है और कम बनाए रखने योग्य कोड की ओर जाता है - जब तक कि आप एक स्थानीय चर को संशोधित नहीं कर रहे हैं और आपका एल्गोरिथ्म छोटा और / या तुच्छ है, जो कोड स्थिरता के सवाल के दायरे से बाहर है - कार्यात्मक प्रोग्रामिंग के लिए एक अच्छा परिचय प्राप्त करें (सैकड़ों हैं) और पढ़ना शुरू करें। एक "पूर्वावलोकन" स्पष्टीकरण कुछ इस तरह होगा: यह अधिक गणितीय रूप से ध्वनि और आसान कारण है जिसके बारे में डेटा (आपके कार्यक्रम के अधिकांश हिस्सों में) को संशोधित नहीं करने के लिए और उच्च स्तर और कम तकनीकी (साथ ही अधिक मानव अनुकूल, एक बार आपके मस्तिष्क में ले जाता है) पुरानी शैली की अनिवार्य सोच से दूर संक्रमण) कार्यक्रम के तर्क की परिभाषा।
एरिक अल्लिक ने पहले से ही बहुत अच्छे कारण दिए हैं, आप एक स्ट्रीम के तत्वों को मौजूदा सूची में क्यों नहीं एकत्र करना चाहते हैं।
वैसे भी, आप निम्नलिखित एक-लाइनर का उपयोग कर सकते हैं, अगर आपको वास्तव में इस कार्यक्षमता की आवश्यकता है।
लेकिन जैसा कि स्टुअर्ट मार्क्स अपने जवाब में बताते हैं, आपको ऐसा कभी नहीं करना चाहिए , अगर धाराएं समानांतर धाराएं हो सकती हैं - अपने जोखिम पर उपयोग करें ...
list.stream().collect(Collectors.toCollection(() -> myExistingList));
आपको बस अपनी मूल सूची का उल्लेख करना होगा जो कि 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())
);
यही कार्य कार्यात्मक प्रोग्रामिंग प्रतिमान प्रदान करता है।
targetList = sourceList.stream ()। फ़्लैटमैप (सूची :: स्ट्रीम) .collect (कलेक्टर .toList ());
मैं पुरानी सूची और नई सूची को धाराओं के रूप में संक्षिप्त करूंगा और परिणामों को गंतव्य सूची में सहेजूंगा। समानांतर में भी अच्छा काम करता है।
मैं स्टुअर्ट मार्क्स द्वारा दिए गए स्वीकृत उत्तर के उदाहरण का उपयोग करूंगा:
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]
आशा करता हूँ की ये काम करेगा।
Collection
"