अगर जावा 8 स्ट्रीम खाली है तो कैसे जांचें?


95

अगर कोई Streamखाली नहीं है तो मैं कैसे जांच कर सकता हूं कि वह गैर-टर्मिनल ऑपरेशन के रूप में खाली है या नहीं।

मूल रूप से, मैं नीचे दिए गए कोड के बराबर कुछ देख रहा हूं, लेकिन धारा के बीच में सामग्री के बिना। विशेष रूप से, एक टर्मिनल ऑपरेशन द्वारा स्ट्रीम वास्तव में खपत होने से पहले चेक नहीं होना चाहिए।

public Stream<Thing> getFilteredThings() {
    Stream<Thing> stream = getThings().stream()
                .filter(Thing::isFoo)
                .filter(Thing::isBar);
    return nonEmptyStream(stream, () -> {
        throw new RuntimeException("No foo bar things available")   
    });
}

private static <T> Stream<T> nonEmptyStream(Stream<T> stream, Supplier<T> defaultValue) {
    List<T> list = stream.collect(Collectors.toList());
    if (list.isEmpty()) list.add(defaultValue.get());
    return list.stream();
}

23
आपके पास अपना केक नहीं हो सकता है और वह भी खा सकता है - और इस संदर्भ में काफी शाब्दिक रूप से। खाली होने का पता लगाने के लिए आपको स्ट्रीम का उपभोग करना होगा। यह स्ट्रीम के शब्दार्थ (आलस्य) की बात है।
मार्को टोपोलनिक

अंततः इसका सेवन किया जाएगा, इस बिंदु पर चेक होना चाहिए
सेफेलोपॉड

11
यह जांचने के लिए कि धारा खाली नहीं है, आपको कम से कम एक तत्व का उपभोग करने का प्रयास करना होगा। उस बिंदु पर धारा अपनी "कौमार्य" खो चुकी है और शुरू से फिर से उपभोग नहीं किया जा सकता है।
मार्को टोपोलनिक

जवाबों:


24

यदि आप सीमित समानांतर कैपब्लॉबिलिटी के साथ रह सकते हैं, तो निम्न समाधान काम करेगा:

private static <T> Stream<T> nonEmptyStream(
    Stream<T> stream, Supplier<RuntimeException> e) {

    Spliterator<T> it=stream.spliterator();
    return StreamSupport.stream(new Spliterator<T>() {
        boolean seen;
        public boolean tryAdvance(Consumer<? super T> action) {
            boolean r=it.tryAdvance(action);
            if(!seen && !r) throw e.get();
            seen=true;
            return r;
        }
        public Spliterator<T> trySplit() { return null; }
        public long estimateSize() { return it.estimateSize(); }
        public int characteristics() { return it.characteristics(); }
    }, false);
}

यहाँ कुछ उदाहरण कोड का उपयोग किया गया है:

List<String> l=Arrays.asList("hello", "world");
nonEmptyStream(l.stream(), ()->new RuntimeException("No strings available"))
  .forEach(System.out::println);
nonEmptyStream(l.stream().filter(s->s.startsWith("x")),
               ()->new RuntimeException("No strings available"))
  .forEach(System.out::println);

(कुशल) समानांतर निष्पादन के साथ समस्या यह है कि बंटवारे का समर्थन करने के लिए Spliteratorथ्रेड-सुरक्षित तरीके की आवश्यकता होती है ताकि यह ध्यान दिया जा सके कि किसी भी टुकड़े ने थ्रेड-सुरक्षित तरीके से कोई मूल्य देखा है या नहीं। तब निष्पादित किए गए टुकड़ों में से अंतिम को tryAdvanceयह महसूस करना होगा कि उपयुक्त अपवाद को फेंकने के लिए यह अंतिम है (और यह भी अग्रिम नहीं हो सकता है)। इसलिए मैंने यहाँ विभाजन के लिए समर्थन नहीं जोड़ा।


33

अन्य उत्तर और टिप्पणियाँ सही हैं कि किसी स्ट्रीम की सामग्री की जांच करने के लिए, एक टर्मिनल ऑपरेशन जोड़ना होगा, जिससे स्ट्रीम को "उपभोग" किया जा सके। हालांकि, कोई भी ऐसा कर सकता है और परिणाम को एक धारा में बदल सकता है, बिना स्ट्रीम के पूरी सामग्री को बफर किए बिना। यहाँ कुछ उदाहरण दिए गए हैं:

static <T> Stream<T> throwIfEmpty(Stream<T> stream) {
    Iterator<T> iterator = stream.iterator();
    if (iterator.hasNext()) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
    } else {
        throw new NoSuchElementException("empty stream");
    }
}

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) {
    Iterator<T> iterator = stream.iterator();
    if (iterator.hasNext()) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
    } else {
        return Stream.of(supplier.get());
    }
}

मूल रूप से उस पर Iteratorकॉल करने के लिए धारा को एक में बदल hasNext()दें, और यदि सही है, तो Iteratorवापस को एक में बदल दें Stream। यह अक्षम है कि धारा पर सभी बाद के संचालन Iterator's hasNext()और next()विधियों के माध्यम से जाएंगे , जिसका अर्थ यह भी है कि धारा को प्रभावी ढंग से क्रमिक रूप से संसाधित किया जाता है (भले ही यह बाद में समानांतर हो)। हालाँकि, यह आपको इसके सभी तत्वों को बफर किए बिना स्ट्रीम का परीक्षण करने की अनुमति देता है।

संभवतः ए के Spliteratorबजाय इसका उपयोग करने का एक तरीका है Iterator। यह संभावित रूप से रिटर्न स्ट्रीम को समान रूप से चलाने सहित इनपुट स्ट्रीम के समान विशेषताओं की अनुमति देता है।


1
मुझे नहीं लगता है कि एक अनुरक्षण समाधान है जो कुशल समानांतर प्रसंस्करण का समर्थन करेगा क्योंकि विभाजन का समर्थन करना कठिन है, हालांकि होने estimatedSizeऔर characteristicsएकल-थ्रेडेड प्रदर्शन में सुधार भी हो सकता है। यह बस हो गया कि मैंने लिखा Spliteratorसमाधान करते समय आप पोस्टिंग गया Iteratorसमाधान ...
होल्गर

3
आप एक Spliterator के लिए स्ट्रीम पूछ सकते हैं, tryAdvance (lambda) को कॉल कर सकते हैं, जहाँ पर आपका लैम्ब्डा कुछ भी पास करता है, और उसके बाद एक Spliterator को लौटाता है जो अंतर्निहित स्प्लिटर को लगभग सब कुछ दर्शाता है, सिवाय इसके कि वह पहले तत्व को पहली बार वापस glunk करता है ( और अनुमान का परिणाम तय करता है)।
ब्रायन गोएट्ज़

1
@BrianGoetz हां, यह मेरा विचार था, मैंने अभी तक उन सभी विवरणों को संभालने के पैर के काम से जाने की जहमत नहीं उठाई है।
स्टुअर्ट मार्क्स

3
@ ब्रायन गोएत्ज़: यही मेरा मतलब है "बहुत जटिल" के साथ। कॉल करने tryAdvanceसे पहले Streamयह Streamएक "आंशिक रूप से आलसी" स्ट्रीम में आलसी प्रकृति को बदल देता है। इसका तात्पर्य यह भी है कि पहले तत्व की खोज करना एक समानांतर ऑपरेशन नहीं है क्योंकि आपको पहले विभाजन करना है और tryAdvanceएक वास्तविक समानांतर ऑपरेशन करने के लिए समवर्ती भागों पर करना है, जहां तक ​​मैंने समझा था। यदि एकमात्र टर्मिनल ऑपरेशन findAnyया समान है जो पूरे parallel()अनुरोध को नष्ट कर देगा ।
होल्गर

2
इसलिए पूर्ण समानांतर समर्थन के लिए आपको tryAdvanceस्ट्रीम करने से पहले कॉल नहीं करना चाहिए और प्रत्येक स्प्लिट भाग को एक प्रॉक्सी में लपेटना होगा और अपने आप सभी समवर्ती संचालन की "hasAny" जानकारी इकट्ठा करना होगा और यह सुनिश्चित करना होगा कि अंतिम समवर्ती ऑपरेशन वांछित अपवाद फेंकता है यदि धारा खाली थी। बहुत सारा सामान ...
होल्गर

18

यह कई मामलों में पर्याप्त हो सकता है

stream.findAny().isPresent()

15

किसी भी फ़िल्टर को लागू करने के लिए आपको स्ट्रीम पर एक टर्मिनल ऑपरेशन करना होगा। इसलिए जब तक आप इसका उपभोग नहीं करेंगे तब तक आप यह नहीं जान सकते कि यह खाली होगा।

सबसे अच्छा आप एक findAny()टर्मिनल ऑपरेशन के साथ स्ट्रीम को समाप्त कर सकते हैं , जो किसी भी तत्व को खोजने पर बंद हो जाएगा, लेकिन अगर कोई नहीं है, तो इसे खोजने के लिए सभी इनपुट सूची पर पुनरावृति करना होगा।

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

निश्चित रूप से आपको आउटपुट सूची बनाने के लिए एक नई स्ट्रीम बनानी होगी।


7
वहाँ anyMatch(alwaysTrue()), मुझे लगता है कि यह सबसे करीब है hasAny
मार्को टोपोलनिक

1
@MarkoTopolnik ने केवल संदर्भ की जाँच की - मेरे मन में जो भी था वह findAny () था, हालांकि कोई भीMatch () भी काम करेगा।
एरन

3
anyMatch(alwaysTrue())पूरी तरह से hasAnyआप के booleanबदले के इरादों से मेल खाता है , आपको इसके बजाय Optional<T>--- लेकिन हम यहाँ बालों को विभाजित कर रहे हैं :)
मार्को टोपोल्निक

1
नोट alwaysTrueएक अमरुद की भविष्यवाणी है।
जीन-फ्रांस्वा सवार्ड

10
anyMatch(e -> true)फिर।
FBB

5

मुझे लगता है कि एक बूलियन को मैप करने के लिए पर्याप्त होना चाहिए

कोड में यह है:

boolean isEmpty = anyCollection.stream()
    .filter(p -> someFilter(p)) // Add my filter
    .map(p -> Boolean.TRUE) // For each element after filter, map to a TRUE
    .findAny() // Get any TRUE
    .orElse(Boolean.FALSE); // If there is no match return false

1
अगर यह सब आप की जरूरत है, kenglxn का जवाब बेहतर है।
डोमिनीकस मोस्टस्किस

इसका बेकार, यह Collection.isEmpty ()
Krzysiek

@Krzysiek यह बेकार नहीं है अगर आपको संग्रह को फ़िल्टर करने की आवश्यकता है। हालाँकि, मैं इस बात पर सहमत हूँ कि डोमिनिकास उस kenglxn के जवाब से बेहतर है
हर्ट्ज़ू

यह इसलिए है क्योंकि यह भी डुप्लिकेटStream.anyMatch()
Krzysiek

4

स्टुअर्ट के विचार के बाद, इसे इस Spliteratorतरह से किया जा सकता है :

static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Stream<T> defaultStream) {
    final Spliterator<T> spliterator = stream.spliterator();
    final AtomicReference<T> reference = new AtomicReference<>();
    if (spliterator.tryAdvance(reference::set)) {
        return Stream.concat(Stream.of(reference.get()), StreamSupport.stream(spliterator, stream.isParallel()));
    } else {
        return defaultStream;
    }
}

मुझे लगता है कि यह समानांतर धाराओं के साथ काम करता है क्योंकि stream.spliterator()ऑपरेशन धारा को समाप्त कर देगा, और फिर आवश्यकता के अनुसार इसका पुनर्निर्माण करेगा

मेरे उपयोग-मामले में मुझे Streamडिफ़ॉल्ट मान के बजाय डिफ़ॉल्ट की आवश्यकता थी । यह बदलना काफी आसान है अगर यह वह नहीं है जो आपको चाहिए


मैं यह पता नहीं लगा सकता कि क्या यह समानांतर धाराओं के साथ प्रदर्शन को प्रभावित करेगा। शायद यह परीक्षण करना चाहिए अगर यह एक आवश्यकता है
फोनिक्स7360

क्षमा करें, यह महसूस नहीं किया कि @Holger के पास एक समाधान भी था और Spliteratorमुझे आश्चर्य हुआ कि दोनों की तुलना कैसे हुई।
फोनिक्स7360
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.