วิธีตรวจสอบว่า Java 8 Stream ว่างเปล่าหรือไม่?


100

ฉันจะตรวจสอบได้อย่างไรว่า a 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
คุณไม่สามารถมีเค้กของคุณและกินมันได้ - และในบริบทนี้ก็ค่อนข้างเป็นเช่นนั้น คุณต้องใช้สตรีมเพื่อดูว่าว่างเปล่า นั่นคือประเด็นของความหมายของสตรีม (ความเกียจคร้าน)
Marko Topolnik

มันจะถูกใช้ในที่สุด ณ จุดนี้การตรวจสอบควรเกิดขึ้น
Cephalopod

12
ในการตรวจสอบว่าสตรีมไม่ว่างเปล่าคุณต้องพยายามใช้อย่างน้อยหนึ่งองค์ประกอบ เมื่อถึงจุดนั้นกระแสได้สูญเสีย "ความบริสุทธิ์" และไม่สามารถบริโภคได้อีกตั้งแต่เริ่มต้น
Marko Topolnik

คำตอบ:


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 hasNext()และnext()วิธีการซึ่งหมายความว่าสตรีมได้รับการประมวลผลตามลำดับอย่างมีประสิทธิภาพ (แม้ว่าจะเปลี่ยนเป็นแบบขนานในภายหลังก็ตาม) อย่างไรก็ตามวิธีนี้ช่วยให้คุณสามารถทดสอบสตรีมได้โดยไม่ต้องบัฟเฟอร์องค์ประกอบทั้งหมด

อาจมีวิธีดำเนินการโดยใช้SpliteratorแทนIteratorไฟล์. สิ่งนี้อาจทำให้สตรีมที่ส่งคืนมีลักษณะเดียวกับสตรีมอินพุตรวมถึงการทำงานแบบขนาน


1
ฉันไม่คิดว่าจะมีโซลูชันที่ดูแลรักษาได้ซึ่งจะรองรับการประมวลผลแบบขนานที่มีประสิทธิภาพเนื่องจากเป็นการยากที่จะรองรับการแยกอย่างไรก็ตามมีestimatedSizeและcharacteristicsอาจปรับปรุงประสิทธิภาพเธรดเดียว มันเพิ่งเกิดขึ้นที่ฉันเขียนSpliteratorวิธีแก้ปัญหาในขณะที่คุณกำลังโพสต์Iteratorวิธีแก้ปัญหา ...
Holger

3
คุณสามารถขอสตรีมสำหรับ Spliterator เรียก tryAdvance (แลมบ์ดา) ที่แลมด้าของคุณจับทุกสิ่งที่ส่งผ่านไปแล้วส่งคืน Spliterator ที่มอบหมายเกือบทุกอย่างให้กับ Spliterator ที่อยู่เบื้องหลังยกเว้นว่าจะติดกาวองค์ประกอบแรกกลับไปที่ชิ้นส่วนแรก ( และแก้ไขผลลัพธ์ของการประมาณการขนาด)
Brian Goetz

1
@BrianGoetz ใช่นั่นคือความคิดของฉันฉันยังไม่ได้ใส่ใจในการจัดการรายละเอียดทั้งหมดเหล่านั้น
Stuart Marks

3
@Brian Goetz: นั่นคือสิ่งที่ฉันหมายถึง "ซับซ้อนเกินไป" การโทรtryAdvanceก่อนที่Streamจะเปลี่ยนนิสัยขี้เกียจให้Streamกลายเป็นกระแส "ขี้เกียจบางส่วน" นอกจากนี้ยังบอกเป็นนัยว่าการค้นหาองค์ประกอบแรกไม่ใช่การดำเนินการแบบขนานอีกต่อไปเนื่องจากคุณต้องแยกก่อนและทำtryAdvanceในส่วนที่แยกพร้อมกันเพื่อทำการดำเนินการแบบขนานจริงเท่าที่ฉันเข้าใจ หากการดำเนินการเทอร์มินัล แต่เพียงผู้เดียวfindAnyหรือคล้ายกันซึ่งจะทำลายparallel()คำขอทั้งหมด
Holger

2
ดังนั้นสำหรับการสนับสนุนแบบขนานเต็มรูปแบบคุณต้องไม่โทรtryAdvanceก่อนที่สตรีมจะทำและต้องรวมทุกส่วนที่แยกเป็นพร็อกซีและรวบรวมข้อมูล "hasAny" ของการดำเนินการพร้อมกันทั้งหมดด้วยตัวคุณเองและตรวจสอบให้แน่ใจว่าการดำเนินการพร้อมกันครั้งสุดท้ายจะทำให้เกิดข้อยกเว้นที่ต้องการหาก สตรีมว่างเปล่า เพียบ…
Holger


15

คุณต้องดำเนินการเทอร์มินัลบนสตรีมเพื่อให้สามารถใช้ตัวกรองใด ๆ ได้ ดังนั้นคุณไม่สามารถรู้ได้ว่ามันจะว่างเปล่าจนกว่าคุณจะบริโภคมัน

สิ่งที่ดีที่สุดที่คุณทำได้คือยุติการสตรีมด้วยการfindAny()ดำเนินการเทอร์มินัลซึ่งจะหยุดเมื่อพบองค์ประกอบใด ๆ แต่ถ้าไม่มีก็จะต้องวนซ้ำในรายการอินพุตทั้งหมดเพื่อค้นหาว่า

สิ่งนี้จะช่วยคุณได้ก็ต่อเมื่อรายการอินพุตมีองค์ประกอบจำนวนมากและหนึ่งในไม่กี่รายการแรกผ่านตัวกรองเนื่องจากจะต้องใช้รายการย่อยเพียงเล็กน้อยก่อนที่คุณจะรู้ว่าสตรีมไม่ว่างเปล่า

แน่นอนว่าคุณจะต้องสร้างสตรีมใหม่เพื่อสร้างรายการผลลัพธ์


7
นอกจากนี้ผมคิดว่าเป็นเรื่องที่ใกล้เคียงกับanyMatch(alwaysTrue()) hasAny
Marko Topolnik

1
@MarkoTopolnik เพิ่งตรวจสอบข้อมูลอ้างอิง - สิ่งที่ฉันคิดไว้คือ findAny () แม้ว่า anyMatch () ก็ใช้ได้เช่นกัน
Eran

3
anyMatch(alwaysTrue())ตรงกับความหมายที่คุณตั้งใจไว้อย่างสมบูรณ์hasAnyโดยให้คุณbooleanแทนOptional<T>--- แต่เรากำลังแยกเส้นขนที่นี่ :)
Marko Topolnik

1
หมายเหตุalwaysTrueเป็นเพรดิเคต Guava
Jean-François Savard

11
anyMatch(e -> true)แล้ว.
FBB

6

ฉันคิดว่าควรจะเพียงพอที่จะแมปบูลีน

ในรหัสนี้คือ:

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 จะดีกว่า
Dominykas Mostauskis

มันไร้ประโยชน์มันซ้ำ Collection.isEmpty ()
Krzysiek

@Krzysiek มันไม่มีประโยชน์ถ้าคุณต้องการกรองคอลเลกชัน อย่างไรก็ตามฉันเห็นด้วยกับ Dominykas ว่าคำตอบของ kenglxn นั้นดีกว่า
Hertzu

เป็นเพราะซ้ำกันด้วยStream.anyMatch()
Krzysiek

4

ตามแนวคิดของ Stuart สิ่งนี้สามารถทำได้ด้วยสิ่ง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มากกว่าค่าเริ่มต้น มันค่อนข้างง่ายที่จะเปลี่ยนหากนี่ไม่ใช่สิ่งที่คุณต้องการ


ฉันคิดไม่ออกว่าสิ่งนี้จะส่งผลต่อประสิทธิภาพของสตรีมคู่ขนานอย่างมากหรือไม่ น่าจะทดสอบได้ว่าเป็นข้อกำหนดหรือไม่
phoenix7360

ขออภัยไม่ทราบว่า @Holger มีวิธีแก้ปัญหาด้วยSpliteratorฉันสงสัยว่าทั้งสองเปรียบเทียบกันอย่างไร
phoenix7360

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.