Iterable <T> स्ट्रीम () और पैरेललस्ट्रीम () तरीके क्यों नहीं प्रदान करता है?


241

मैं सोच रहा हूं कि Iterableइंटरफ़ेस क्यों stream()और parallelStream()तरीके प्रदान नहीं करता है । निम्नलिखित वर्ग पर विचार करें:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}

यह एक हाथ का कार्यान्वयन है क्योंकि ट्रेडिंग कार्ड गेम खेलते समय आपके हाथ में कार्ड हो सकते हैं।

अनिवार्य रूप से यह एक लपेटता है List<Card>, एक अधिकतम क्षमता सुनिश्चित करता है और कुछ अन्य उपयोगी सुविधाएँ प्रदान करता है। इसे सीधे तौर पर लागू करना बेहतर है List<Card>

अब, convienience के लिए मुझे लगा कि इसे लागू करना अच्छा होगा Iterable<Card>, अगर आप इसके लिए लूप करना चाहते हैं, तो आप एन्हांस्ड लूप का उपयोग कर सकते हैं। (मेरी Handकक्षा भी एक get(int index)विधि प्रदान करती है , इसलिए Iterable<Card>मेरी राय में यह उचित है।)

Iterableइंटरफ़ेस निम्नलिखित (जावाडोक बाहर छोड़ दिया) प्रदान करता है:

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

अब आप इसके साथ एक स्ट्रीम प्राप्त कर सकते हैं:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

तो असली सवाल पर:

  • Iterable<T>एक डिफ़ॉल्ट तरीके क्यों नहीं प्रदान करता है जो लागू करते हैं stream()और parallelStream(), मुझे ऐसा कुछ भी दिखाई देता है जो इसे असंभव या अवांछित बना देगा?

एक संबंधित प्रश्न जो मुझे मिला वह निम्नलिखित है: हालांकि स्ट्रीम <T> Iterable <T> को लागू क्यों नहीं करता है?
यह अजीब तरह से पर्याप्त है जो इसे कुछ हद तक दूसरे तरीके से करने का सुझाव दे रहा है।


1
मुझे लगता है कि यह लैम्ब्डा मेलिंग सूची के लिए एक अच्छा सवाल है ।
एडविन डेलोरजो

एक धारा पर पुनरावृति करना क्यों अजीब है? आप संभवतः break;एक पुनरावृत्ति कैसे कर सकते हैं ? (ठीक है, Stream.findFirst()एक समाधान हो सकता है, लेकिन यह सभी जरूरतों को पूरा नहीं कर सकता है ...)
glglgl

जवाबों:


298

यह कोई चूक नहीं थी; 2013 की जून में ईजी सूची पर विस्तृत चर्चा हुई।

विशेषज्ञ समूह की निश्चित चर्चा इस सूत्र में निहित है ।

जबकि यह "स्पष्ट" (विशेषज्ञ समूह के लिए भी शुरू में) लग रहा था stream() लग रहा था यह समझ में आ रहा था Iterable, तथ्य यह है कि Iterableइतना सामान्य हो गया था, क्योंकि स्पष्ट हस्ताक्षर:

Stream<T> stream()

हमेशा वह नहीं था जो आप चाहते थे। कुछ चीजें जो थींIterable<Integer>IntStreamउदाहरण के लिए, उनकी धारा पद्धति की वापसी होती हैं , उदाहरण के लिए। लेकिन stream()इस पद्धति को उच्च पदानुक्रम में रखने से यह असंभव हो जाएगा। तो इसके बजाय, हम यह वास्तव में आसान एक बनाने के लिए बनाया Streamएक से Iterable, एक प्रदान करके spliterator()विधि। के कार्यान्वयन stream()में Collectionसिर्फ यह है:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

कोई भी ग्राहक उस स्ट्रीम को प्राप्त कर सकता है जिसे वे चाहते हैं Iterable:

Stream s = StreamSupport.stream(iter.spliterator(), false);

अंत में हम निष्कर्ष निकाला है कि जोड़ने stream()के लिए Iterableएक गलती होगी।


8
मैं देख रहा हूँ, सबसे पहले, सवाल का जवाब देने के लिए बहुत बहुत धन्यवाद। मैं अभी भी उत्सुक हूँ कि क्यों एक Iterable<Integer>(मुझे लगता है कि आप के बारे में बात कर रहे हैं?) एक वापसी करना चाहते हैं IntStream। क्या पुन: चलने योग्य होगा, बल्कि नहीं होगा PrimitiveIterator.OfInt? या आप शायद एक और usecase मतलब है?
skiwi

139
मुझे यह अजीब लगता है कि उपरोक्त तर्क कथित रूप से Iterable पर लागू किया गया था (मेरे पास स्ट्रीम नहीं हो सकता है) (क्योंकि कोई व्यक्ति इसे IntStream पर वापस लौटा सकता है) जबकि संग्रह के लिए सटीक समान विधि को जोड़ने के लिए विचार की एक समान मात्रा नहीं दी गई थी ( मैं चाहता हूं कि मेरा कलेक्शन <Integer> की स्ट्रीम () इंट्रस्ट भी लौटाया जाए। चाहे वह दोनों पर मौजूद हो या दोनों पर अनुपस्थित हो, लोगों को शायद अपने जीवन के साथ बस मिल गया होगा, लेकिन क्योंकि यह एक पर मौजूद है और अनुपस्थित है। अन्य, यह काफी
चकाचौंध भरी

6
ब्रायन मैककिनटन: यह मेरे लिए अधिक समझ में आता है। ऐसा लगता है कि लोग बहस करते-करते थक गए और इसे सुरक्षित खेलने का फैसला किया।
जोनाथन लोके

44
हालांकि यह समझ में आता है, क्या कोई कारण है कि कोई वैकल्पिक स्थैतिक नहीं है Stream.of(Iterable), जो एपीआई प्रलेखन को पढ़कर कम से कम इस विधि को उचित रूप से खोज करने योग्य बना देगा - जैसा कि किसी ने वास्तव में कभी भी धाराओं के आंतरिक के साथ काम नहीं किया है। यहां तक ​​कि देखा गया StreamSupport, जिसे "निम्न-स्तरीय संचालन" प्रदान करने के रूप में प्रलेखन में वर्णित किया गया है जो "ज्यादातर पुस्तकालय लेखकों के लिए" हैं।
जूल्स

9
मैं जूल्स से पूरी तरह सहमत हूं। एक स्थिर विधि Stream.of (Iteratable iter) या Stream.of (Iterator iter) को StreamSupport.stream (iter.spliterator (), false) के बजाय जोड़ा जाना चाहिए;
user_3380739 22

23

मैंने प्रोजेक्ट लैंबडा मेलिंग सूचियों में से कई में एक जांच की और मुझे लगता है कि मुझे कुछ दिलचस्प चर्चाएँ मिलीं।

मुझे अब तक कोई संतोषजनक स्पष्टीकरण नहीं मिला है। यह सब पढ़ने के बाद मैंने निष्कर्ष निकाला कि यह सिर्फ एक चूक थी। लेकिन आप यहां देख सकते हैं कि एपीआई के डिजाइन के दौरान वर्षों में कई बार चर्चा की गई थी।

लैम्ब्डा लिब्स के स्पश्ट विशेषज्ञ

मुझे इस बारे में चर्चा मिली कि लैंबडा लिब्स के स्पेशलिस्ट मेलिंग लिस्ट में :

Iterable / Iterator.stream के तहत () सैम पुल्लारा ने कहा:

मैं ब्रायन के साथ काम कर रहा था, यह देखने के लिए कि किस तरह से लिमिट / सबस्ट्रीम फंक्शनलिटी [1] लागू की जा सकती है और उन्होंने सुझाव दिया कि इटरेटर में रूपांतरण इसके बारे में जाने का सही तरीका है। मैंने उस समाधान के बारे में सोचा था, लेकिन एक पुनरावृत्ति लेने और इसे एक धारा में बदलने का कोई स्पष्ट तरीका नहीं मिला। यह पता चला है कि यह वहां है, आपको बस इट्रेटर को एक विभाजक में बदलने की जरूरत है और फिर विभाजक को एक धारा में परिवर्तित करें। इसलिए यह मुझे इस बात पर फिर से विचार करने के लिए लाता है कि क्या हमें इन दोनों में से किसी एक को Iterable / Iterator से सीधे लटका देना चाहिए या दोनों।

मेरा सुझाव कम से कम इटरेटर पर है ताकि आप दोनों दुनियाओं के बीच सफाई से आगे बढ़ सकें और ऐसा करने के बजाय यह आसानी से खोजा जा सकेगा:

Streams.stream (Spliterators.spliteratorUnognSize (पुनरावृति, Spliterator.ORDERED)

और फिर ब्रायन गोएट्ज़ ने जवाब दिया :

मुझे लगता है कि सैम की बात यह थी कि पुस्तकालय की बहुत सारी कक्षाएं हैं जो आपको एक Iterator देती हैं, लेकिन जरूरी नहीं कि आप अपना स्वयं का स्प्लिटर भी लिखें। तो आप सभी कर सकते हैं कॉल स्ट्रीम (spliteratorUnognSize (पुनरावृत्त))। सैम सुझाव दे रहा है कि हम Iterator.stream को परिभाषित करें () आपके लिए।

मैं पुस्तकालय लेखकों / उन्नत उपयोगकर्ताओं के लिए स्ट्रीम () और स्प्लिटर () विधियों को रखना चाहूंगा।

और बादमें

"यह देखते हुए कि एक स्प्लिटर लिखने से एक Iterator लिखना आसान है, मैं एक Iterator के बजाय एक स्प्लिटेटर लिखना पसंद करूंगा (Iterator is so 90s :)"

आप इस बिंदु को याद कर रहे हैं, हालांकि। वहाँ बाहर वर्गों के लाखों रहे हैं कि पहले से ही आप एक Iterator हाथ। और उनमें से कई स्प्लिटर-रेडी नहीं हैं।

पिछले चर्चा में लैंबडा मेलिंग लिस्ट

यह वह उत्तर नहीं हो सकता है जिसकी आप तलाश कर रहे हैं, लेकिन प्रोजेक्ट लैंबडा मेलिंग सूची में इस पर संक्षिप्त चर्चा की गई थी। शायद यह इस विषय पर व्यापक चर्चा को बढ़ावा देने में मदद करता है।

Iterable से धाराओं के तहत ब्रायन Goetz के शब्दों में :

पीछे की और हटना...

स्ट्रीम बनाने के बहुत सारे तरीके हैं। आपके पास तत्वों का वर्णन करने के तरीके के बारे में अधिक जानकारी, अधिक कार्यक्षमता और प्रदर्शन लाइब्रेरी आपको दे सकती है। कम से कम अधिकांश जानकारी के लिए, वे हैं:

इटरेटर

इटरेटर + आकार

Spliterator

Spliterator जो इसके आकार को जानता है

Spliterator जो इसके आकार को जानता है, और आगे जानता है कि सभी उप-विभाजन उनके आकार को जानते हैं।

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

यदि Iterable में एक स्ट्रीम () विधि होती है, तो यह केवल एक Iterator को Spliterator के साथ लपेटेगा, जिसमें कोई आकार की जानकारी नहीं होगी। लेकिन, ज्यादातर चीजें जो Iterable हैं उनमें आकार की जानकारी होती है। जिसका अर्थ है कि हम कमी धाराओं की सेवा कर रहे हैं। इतना अच्छा नहीं है।

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

Iterable द्वारा प्रदान किया गया डिफ़ॉल्ट वास्तव में एक भद्दा होगा - यह आकार को त्याग देगा भले ही Iterables के विशाल बहुमत को उस जानकारी का पता हो।

अंतर्विरोध?

हालांकि, ऐसा लगता है कि चर्चा उन परिवर्तनों पर आधारित है जो विशेषज्ञ समूह ने धाराओं के प्रारंभिक डिजाइन के लिए की थी जो शुरू में पुनरावृत्तियों पर आधारित थी।

फिर भी, यह ध्यान रखना दिलचस्प है कि संग्रह जैसे इंटरफ़ेस में, स्ट्रीम विधि को इस प्रकार परिभाषित किया गया है:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

जो Iterable इंटरफ़ेस में उपयोग किए जा रहे सटीक समान कोड हो सकता है।

इसलिए, इस कारण मैंने कहा कि यह उत्तर शायद संतोषजनक नहीं है, लेकिन फिर भी चर्चा के लिए दिलचस्प है।

रिफैक्टरिंग के साक्ष्य

मेलिंग सूची में विश्लेषण के साथ जारी है, ऐसा लगता है कि स्प्लिटइंटरेटर विधि मूल रूप से संग्रह इंटरफ़ेस में थी, और 2013 में कुछ बिंदु पर वे इसे Iterable तक ले गए।

स्प्लिटइंटरेटर को कलेक्शन से इटरटेबल तक खींचो

निष्कर्ष / सिद्धांतों?

तब संभावना है कि Iterable में विधि की कमी सिर्फ एक चूक है, क्योंकि ऐसा लगता है कि उन्हें स्ट्रीम विधि को स्थानांतरित करने के साथ-साथ विभाजन से Iterable तक विभाजन को स्थानांतरित करना चाहिए।

यदि अन्य कारण हैं जो स्पष्ट नहीं हैं। किसी और के पास अन्य सिद्धांत हैं?


मैं आपकी प्रतिक्रिया की सराहना करता हूं, लेकिन मैं वहां के तर्क से असहमत हूं। इस समय कि आप के ओवरराइड करते spliterator()हैं Iterable, तो वहां सभी मुद्दे तय हो जाते हैं, और आप तुच्छ रूप से लागू कर सकते हैं stream()और parallelStream()..
skiwi

@skiwi इसीलिए मैंने कहा कि शायद इसका जवाब नहीं है। मैं सिर्फ चर्चा में जोड़ने की कोशिश कर रहा हूं, क्योंकि यह जानना मुश्किल है कि विशेषज्ञ समूह ने फैसले क्यों किए। मुझे लगता है कि हम कर सकते हैं मेलिंग सूची में कुछ फोरेंसिक करने की कोशिश करते हैं और देखते हैं कि क्या हम किसी भी कारण से आ सकते हैं।
एडविन डेलोरजो

1
@skiwi मैंने अन्य मेलिंग सूचियों की समीक्षा की और चर्चा के लिए अधिक साक्ष्य पाए और शायद कुछ विचार जो कुछ निदान को सिद्ध करने में मदद करते हैं।
एडविन डेलोरजो

आपके प्रयासों के लिए धन्यवाद, मुझे वास्तव में सीखना चाहिए कि उन मेलिंग सूचियों के माध्यम से कुशलतापूर्वक कैसे विभाजित किया जाए। यह मदद करेगा कि क्या उन्हें कुछ ... आधुनिक तरीके से, एक मंच या कुछ की तरह कल्पना की जा सकती है, क्योंकि उनमें उद्धरण के साथ सादे पाठ ईमेल पढ़ना बिल्कुल कुशल नहीं है।
स्किवी

6

यदि आप उस आकार को जानते हैं java.util.Collectionजो आप उपयोग कर सकते हैं जो stream()विधि प्रदान करता है :

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

और तब:

new Hand().stream().map(...)

मैंने उसी समस्या का सामना किया और आश्चर्यचकित था कि मेरे Iterableकार्यान्वयन को बहुत आसानी से एक AbstractCollectionकार्यान्वयन तक बढ़ाया जा सकता हैsize() विधि को (सौभाग्य से मेरे पास संग्रह का आकार था :-)

आपको ओवरराइड करने पर भी विचार करना चाहिए Spliterator<E> spliterator()

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