คัดลอกสตรีมเพื่อหลีกเลี่ยง "สตรีมได้รับการดำเนินการแล้วหรือปิด"


121

ฉันต้องการทำสตรีม Java 8 ซ้ำเพื่อที่ฉันจะได้จัดการกับมันสองครั้ง ฉันสามารถcollectเป็นรายการและรับสตรีมใหม่จากสิ่งนั้น

// doSomething() returns a stream
List<A> thing = doSomething().collect(toList());
thing.stream()... // do stuff
thing.stream()... // do other stuff

แต่ฉันคิดว่าควรมีวิธีที่มีประสิทธิภาพ / สง่างามกว่านี้

มีวิธีคัดลอกสตรีมโดยไม่เปลี่ยนเป็นคอลเล็กชันหรือไม่?

ฉันกำลังทำงานกับสตรีมของEithers จริงๆดังนั้นต้องการประมวลผลการฉายภาพด้านซ้ายทางเดียวก่อนที่จะย้ายไปยังการฉายภาพด้านขวาและจัดการด้วยวิธีอื่น แบบนี้ (ซึ่งจนถึงตอนนี้ฉันบังคับให้ใช้toListเคล็ดลับด้วย)

List<Either<Pair<A, Throwable>, A>> results = doSomething().collect(toList());

Stream<Pair<A, Throwable>> failures = results.stream().flatMap(either -> either.left());
failures.forEach(failure -> ... );

Stream<A> successes = results.stream().flatMap(either -> either.right());
successes.forEach(success -> ... );

คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับ "กระบวนการทางเดียว" ได้ไหม ... คุณกำลังใช้วัตถุอยู่หรือไม่? ทำแผนที่พวกเขา? partitionBy () และ groupingBy () สามารถนำคุณไปยัง 2+ รายการได้โดยตรง แต่คุณอาจได้รับประโยชน์จากการทำแผนที่ก่อนหรือเพียงแค่มีจุดแยกการตัดสินใจใน forEach () ของคุณ
AjahnCharles

ในบางกรณีการเปลี่ยนเป็นคอลเล็กชันอาจไม่ใช่ทางเลือกหากเรากำลังจัดการกับสตรีมที่ไม่มีที่สิ้นสุด คุณสามารถหาทางเลือกอื่นสำหรับการบันทึกได้ที่นี่: dzone.com/articles/how-to-replay-java-streams
Miguel Gamboa

คำตอบ:


88

ฉันคิดว่าสมมติฐานของคุณเกี่ยวกับประสิทธิภาพเป็นแบบถอยหลัง คุณจะได้รับการคืนทุนที่มีประสิทธิภาพมหาศาลนี้หากคุณจะใช้ข้อมูลเพียงครั้งเดียวเพราะคุณไม่ต้องจัดเก็บและสตรีมก็ให้การเพิ่มประสิทธิภาพ "ลูปฟิวชั่น" ที่มีประสิทธิภาพซึ่งช่วยให้คุณส่งข้อมูลทั้งหมดได้อย่างมีประสิทธิภาพผ่านไปป์ไลน์

หากคุณต้องการใช้ข้อมูลเดิมซ้ำตามคำจำกัดความคุณจะต้องสร้างข้อมูลสองครั้ง (กำหนด) หรือจัดเก็บข้อมูลนั้น ถ้ามันเกิดขึ้นแล้วในคอลเลกชันที่ดี; จากนั้นทำซ้ำสองครั้งก็ถูก

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

หากคุณต้องการดำเนินการกับข้อมูลเดิมซ้ำ ๆ ให้จัดเก็บหรือจัดโครงสร้างการดำเนินงานของคุณในฐานะผู้บริโภคและดำเนินการดังต่อไปนี้:

stream()...stuff....forEach(e -> { consumerA(e); consumerB(e); });

คุณอาจมองเข้าไปในไลบรารี RxJava เนื่องจากรูปแบบการประมวลผลช่วยให้ "สตรีมฟอร์ก" ประเภทนี้ดีขึ้น


1
บางทีฉันไม่ควรใช้ "ประสิทธิภาพ" ฉันรู้สึกว่าทำไมฉันต้องกังวลกับสตรีม (และไม่เก็บอะไรเลย) ถ้าสิ่งที่ฉันทำคือจัดเก็บข้อมูล ( toList) ทันทีเพื่อให้สามารถประมวลผลได้ ( Eitherกรณี เป็นตัวอย่าง)?
Toby

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

1
แต่การไม่สามารถใช้สตรีมซ้ำทำให้เกิดสถานการณ์ที่ผู้พัฒนาถูกบังคับให้จัดเก็บผลลัพธ์ระดับกลาง (การรวบรวม) เพื่อประมวลผลสตรีมด้วยวิธีที่แตกต่างกันสองวิธี ความหมายที่ว่าสตรีมถูกสร้างขึ้นมากกว่าหนึ่งครั้ง (เว้นแต่คุณจะรวบรวม) ดูเหมือนชัดเจน - เพราะมิฉะนั้นคุณจะไม่ต้องใช้วิธีการรวบรวม
Niall Connaughton

@NiallConnaughton ฉันไม่แน่ใจว่าต้องการประเด็นของคุณ หากคุณต้องการสำรวจสองครั้งต้องมีคนเก็บไว้หรือคุณต้องสร้างใหม่ คุณกำลังแนะนำว่าห้องสมุดควรบัฟเฟอร์ในกรณีที่มีคนต้องการสองครั้งหรือไม่? นั่นจะเป็นเรื่องโง่
Brian Goetz

ไม่ได้แนะนำว่าไลบรารีควรบัฟเฟอร์ แต่บอกว่าการมีสตรีมเป็นแบบครั้งเดียวจะบังคับให้ผู้ที่ต้องการนำสตรีมเมล็ดพันธุ์กลับมาใช้ซ้ำ (เช่น: การแบ่งปันตรรกะการประกาศที่ใช้ในการกำหนด) เพื่อสร้างสตรีมที่ได้รับหลายรายการเพื่อรวบรวม สตรีมเมล็ดพันธุ์หรือมีสิทธิ์เข้าถึงโรงงานผู้ให้บริการที่จะสร้างสตรีมเมล็ดพันธุ์ซ้ำ ตัวเลือกทั้งสองมีจุดเจ็บปวด คำตอบนี้มีรายละเอียดมากขึ้นในหัวข้อ: stackoverflow.com/a/28513908/114200
Niall Connaughton

73

คุณสามารถใช้ตัวแปรโลคัลร่วมกับ a Supplierเพื่อตั้งค่าส่วนทั่วไปของไปป์ไลน์สตรีม

จากhttp://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ :

การใช้สตรีมซ้ำ

ไม่สามารถใช้สตรีม Java 8 ซ้ำได้ ทันทีที่คุณเรียกใช้การดำเนินการของเทอร์มินัลสตรีมจะถูกปิด:

Stream<String> stream = Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> s.startsWith("a"));
stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception

Calling `noneMatch` after `anyMatch` on the same stream results in the following exception:
java.lang.IllegalStateException: stream has already been operated upon or closed
at 
java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at 
java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
at com.winterbe.java8.Streams5.test7(Streams5.java:38)
at com.winterbe.java8.Streams5.main(Streams5.java:28)

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

Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

การเรียกแต่ละครั้งเพื่อget()สร้างสตรีมใหม่ที่เราบันทึกไว้เพื่อเรียกใช้งานเทอร์มินัลที่ต้องการ


2
ทางออกที่ดีและสง่างาม java8-ish มากกว่าโซลูชันที่โหวตมากที่สุด
dylaniato

เพียงหมายเหตุเกี่ยวกับการใช้SupplierหากStreamสร้างขึ้นด้วยลักษณะที่ "เสียค่าใช้จ่าย" คุณจะต้องเสียค่าใช้จ่ายสำหรับการโทรแต่ละSupplier.get()ครั้ง เช่นถ้าแบบสอบถามฐานข้อมูล ... การสืบค้นนั้นเสร็จสิ้นในแต่ละครั้ง
Julien

ดูเหมือนว่าคุณจะไม่สามารถทำตามรูปแบบนี้ได้หลังจากแผนที่แม้ว่าจะใช้ IntStream ก็ตาม ฉันพบว่าฉันต้องแปลงกลับเป็นSet<Integer>ใช้collect(Collectors.toSet())... และดำเนินการสองสามอย่าง ฉันต้องการmax()และถ้าค่าเฉพาะถูกตั้งเป็นสองการดำเนินการ ...filter(d -> d == -1).count() == 1;
JGFMK

16

ใช้ a Supplierเพื่อสร้างสตรีมสำหรับการดำเนินการยกเลิกแต่ละครั้ง

Supplier<Stream<Integer>> streamSupplier = () -> list.stream();

เมื่อใดก็ตามที่คุณต้องการสตรีมของคอลเล็กชันนั้นให้ใช้streamSupplier.get()เพื่อรับสตรีมใหม่

ตัวอย่าง:

  1. streamSupplier.get().anyMatch(predicate);
  2. streamSupplier.get().allMatch(predicate2);

โหวตให้คุณในฐานะที่คุณเป็นคนแรกที่ชี้ให้ซัพพลายเออร์ดูที่นี่
EnzoBnl

9

เราได้ดำเนินการduplicate()วิธีการสำหรับการสตรีมในjOOλ , ห้องสมุดมาเปิดที่เราสร้างขึ้นเพื่อปรับปรุงการทดสอบการรวมสำหรับjOOQ โดยพื้นฐานแล้วคุณสามารถเขียน:

Tuple2<Seq<A>, Seq<A>> duplicates = Seq.seq(doSomething()).duplicate();

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

นี่คือวิธีการทำงานของอัลกอริทึม:

static <T> Tuple2<Seq<T>, Seq<T>> duplicate(Stream<T> stream) {
    final List<T> gap = new LinkedList<>();
    final Iterator<T> it = stream.iterator();

    @SuppressWarnings("unchecked")
    final Iterator<T>[] ahead = new Iterator[] { null };

    class Duplicate implements Iterator<T> {
        @Override
        public boolean hasNext() {
            if (ahead[0] == null || ahead[0] == this)
                return it.hasNext();

            return !gap.isEmpty();
        }

        @Override
        public T next() {
            if (ahead[0] == null)
                ahead[0] = this;

            if (ahead[0] == this) {
                T value = it.next();
                gap.offer(value);
                return value;
            }

            return gap.poll();
        }
    }

    return tuple(seq(new Duplicate()), seq(new Duplicate()));
}

ซอร์สโค้ดเพิ่มเติมที่นี่

Tuple2อาจจะเป็นเหมือนคุณPairชนิดในขณะที่Seqเป็นStreamกับการปรับปรุงบางส่วน


2
โซลูชันนี้ไม่ปลอดภัยต่อเธรด: คุณไม่สามารถส่งสตรีมรายการใดรายการหนึ่งไปยังเธรดอื่นได้ ฉันไม่เห็นสถานการณ์จริง ๆ ที่ทั้งสองสตรีมสามารถใช้ในอัตราที่เท่ากันในเธรดเดียวและคุณต้องการสตรีมที่แตกต่างกันสองสตรีม หากคุณต้องการสร้างผลลัพธ์สองรายการจากสตรีมเดียวกันจะเป็นการดีกว่ามากหากใช้การรวมตัวสะสม (ซึ่งคุณมีอยู่แล้วใน JOOL)
Tagir Valeev

@TagirValeev: คุณพูดถูกเกี่ยวกับความปลอดภัยของเธรดจุดที่ดี จะทำอย่างไรกับการรวมนักสะสม?
Lukas Eder

1
ฉันหมายความว่าถ้าใครต้องการที่จะใช้กระแสเดียวกันสองครั้งเช่นนี้มันจะดีกว่าที่จะTuple2<Seq<A>>, Seq<A>> t = duplicate(stream); long count = t.collect(counting()); List<A> list = t.collect(toList()); Tuple2<Long, List<A>> t = stream.collect(Tuple.collectors(counting(), toList()));การใช้Collectors.mapping/reducingอย่างใดอย่างหนึ่งอาจแสดงการดำเนินการสตรีมอื่น ๆ ในฐานะตัวรวบรวมและองค์ประกอบของกระบวนการในลักษณะที่แตกต่างกันมากในการสร้างทูเพิลผลลัพธ์เดียว ดังนั้นโดยทั่วไปคุณสามารถทำหลาย ๆ อย่างโดยใช้สตรีมเพียงครั้งเดียวโดยไม่ต้องทำซ้ำและจะเป็นแบบคู่ขนาน
Tagir Valeev

2
ในกรณีนี้คุณจะยังคงลดหนึ่งสตรีมต่อไป ดังนั้นจึงไม่มีจุดใดที่จะทำให้ชีวิตยากขึ้นในการแนะนำตัวทำซ้ำที่นุ่มนวลซึ่งจะรวบรวมสตรีมทั้งหมดไว้ในรายการภายใต้ประทุน คุณสามารถรวบรวมไปยังรายการอย่างชัดเจนจากนั้นสร้างสตรีมสองรายการจากนั้นตามที่ OP บอก (เป็นจำนวนบรรทัดรหัสเดียวกัน) คุณอาจมีการปรับปรุงบางอย่างหากการลดครั้งแรกเป็นการลัดวงจร แต่ไม่ใช่กรณีของ OP
Tagir Valeev

1
@maaartinus: ขอบคุณตัวชี้ที่ดี ฉันได้สร้างปัญหาสำหรับเกณฑ์มาตรฐาน ฉันใช้สำหรับoffer()/ poll()API แต่ArrayDequeอาจทำเช่นเดียวกัน
Lukas Eder

7

คุณสามารถสร้างสตรีมของ runnables (ตัวอย่าง):

results.stream()
    .flatMap(either -> Stream.<Runnable> of(
            () -> failure(either.left()),
            () -> success(either.right())))
    .forEach(Runnable::run);

การดำเนินการที่จะใช้failureและอยู่ที่ไหน successอย่างไรก็ตามสิ่งนี้จะสร้างวัตถุชั่วคราวจำนวนมากและอาจไม่มีประสิทธิภาพมากกว่าการเริ่มต้นจากคอลเล็กชันและสตรีม / ทำซ้ำสองครั้ง


4

อีกวิธีหนึ่งในการจัดการองค์ประกอบหลาย ๆ ครั้งคือการใช้ Stream.peek (ผู้บริโภค) :

doSomething().stream()
.peek(either -> handleFailure(either.left()))
.foreach(either -> handleSuccess(either.right()));

peek(Consumer) สามารถล่ามโซ่กี่ครั้งก็ได้ตามต้องการ

doSomething().stream()
.peek(element -> handleFoo(element.foo()))
.peek(element -> handleBar(element.bar()))
.peek(element -> handleBaz(element.baz()))
.foreach(element-> handleQux(element.qux()));

ดูเหมือนว่าไม่ควรใช้
peek

2
@HectorJ หัวข้ออื่น ๆ เกี่ยวกับการปรับเปลี่ยนองค์ประกอบ ฉันสันนิษฐานว่าไม่ได้ทำที่นี่
Martin

2

cyclops-reactซึ่งเป็นไลบรารีที่ฉันมีส่วนร่วมมีวิธีการแบบคงที่ที่จะช่วยให้คุณสามารถทำสตรีมซ้ำได้ (และส่งคืนjOOλ Tuple of Streams)

    Stream<Integer> stream = Stream.of(1,2,3);
    Tuple2<Stream<Integer>,Stream<Integer>> streams =  StreamUtils.duplicate(stream);

ดูความคิดเห็นมีบทลงโทษด้านประสิทธิภาพที่จะเกิดขึ้นเมื่อใช้ซ้ำในสตรีมที่มีอยู่ ทางเลือกที่มีประสิทธิภาพมากขึ้นคือการใช้ Streamable: -

นอกจากนี้ยังมีคลาส Streamable (ขี้เกียจ) ที่สามารถสร้างจาก Stream, Iterable หรือ Array และเล่นซ้ำได้หลายครั้ง

    Streamable<Integer> streamable = Streamable.of(1,2,3);
    streamable.stream().forEach(System.out::println);
    streamable.stream().forEach(System.out::println);

AsStreamable.synchronizedFromStream (สตรีม) - สามารถใช้เพื่อสร้าง Streamable ที่จะเติมข้อมูลคอลเลกชันสำรองอย่างเกียจคร้านในลักษณะที่สามารถแชร์ข้ามเธรดได้ Streamable.fromStream (สตรีม) จะไม่เกิดค่าใช้จ่ายในการซิงโครไนซ์ใด ๆ


2
และแน่นอนว่าควรสังเกตว่าสตรีมที่ได้นั้นมีค่าใช้จ่าย CPU / หน่วยความจำที่สำคัญและประสิทธิภาพการทำงานแบบขนานที่แย่มาก นอกจากนี้โซลูชันนี้ไม่ปลอดภัยต่อเธรด (คุณไม่สามารถส่งสตรีมที่เป็นผลลัพธ์หนึ่งไปยังเธรดอื่นและประมวลผลแบบขนานได้อย่างปลอดภัย) มันจะมีประสิทธิภาพและปลอดภัยกว่ามากList<Integer> list = stream.collect(Collectors.toList()); streams = new Tuple2<>(list.stream(), list.stream())(ตามที่ OP แนะนำ) นอกจากนี้โปรดเปิดเผยอย่างชัดเจนในคำตอบว่าคุณเป็นผู้เขียนไซโคลสตรีม อ่านนี้
Tagir Valeev

อัปเดตเพื่อแสดงว่าฉันเป็นผู้เขียน นอกจากนี้ยังเป็นจุดที่ดีในการหารือเกี่ยวกับลักษณะการทำงานของแต่ละคน การประเมินของคุณข้างต้นค่อนข้างตรงประเด็นสำหรับ StreamUtils.duplicate StreamUtils. duplicate ทำงานโดยการบัฟเฟอร์ข้อมูลจากสตรีมหนึ่งไปยังอีกสตรีมหนึ่งซึ่งมีทั้งค่าใช้จ่ายของ CPU และหน่วยความจำ (กรณีการใช้งานขึ้นอยู่กับ) อย่างไรก็ตามสำหรับ Streamable.of (1,2,3) สตรีมใหม่จะถูกสร้างขึ้นโดยตรงจาก Array ทุกครั้งและลักษณะการทำงานรวมถึงประสิทธิภาพแบบขนานจะเหมือนกับ Stream ที่สร้างขึ้นตามปกติ
John McClean

นอกจากนี้ยังมีคลาส AsStreamable ที่อนุญาตให้สร้างอินสแตนซ์แบบสตรีมได้จากสตรีม แต่ซิงโครไนซ์การเข้าถึงคอลเลกชันที่สำรองข้อมูลสตรีมได้เมื่อสร้างขึ้น (AsStreamable.synchronizedFromStream) ทำให้เหมาะกับการใช้งานข้ามเธรดมากขึ้น (ถ้านั่นคือสิ่งที่คุณต้องการ - ฉันคิดว่า 99% ของเวลาที่สตรีมถูกสร้างขึ้นและใช้ซ้ำในเธรดเดียวกัน)
John McClean

สวัสดี Tagir - คุณไม่ควรเปิดเผยในความคิดเห็นของคุณว่าคุณเป็นผู้เขียนไลบรารีของคู่แข่งหรือไม่?
John McClean

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

0

สำหรับปัญหานี้คุณสามารถใช้การแบ่งพาร์ติชันได้ สิ่งที่ต้องการ

     // Partition Eighters into left and right
     List<Either<Pair<A, Throwable>, A>> results = doSomething();
     Map<Boolean, Object> passingFailing = results.collect(Collectors.partitioningBy(s -> s.isLeft()));
     passingFailing.get(true) <- here will be all passing (left values)
     passingFailing.get(false) <- here will be all failing (right values)

0

เราสามารถใช้ประโยชน์จาก Stream Builder ได้ในขณะที่อ่านหรืออ่านสตรีมซ้ำ นี่คือเอกสารของสร้างกระแส

https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.Builder.html

ใช้กรณี

สมมติว่าเรามีสตรีมของพนักงานและจำเป็นต้องใช้สตรีมนี้เพื่อเขียนข้อมูลพนักงานในไฟล์ excel จากนั้นอัปเดตคอลเลกชัน / ตารางพนักงาน [นี่เป็นเพียงกรณีการใช้งานเพื่อแสดงการใช้ Stream Builder]:

Stream.Builder<Employee> builder = Stream.builder();

employee.forEach( emp -> {
   //store employee data to excel file 
   // and use the same object to build the stream.
   builder.add(emp);
});

//Now this stream can be used to update the employee collection
Stream<Employee> newStream = builder.build();

0

ฉันมีปัญหาที่คล้ายกันและสามารถนึกถึงโครงสร้างระดับกลางที่แตกต่างกันสามแบบเพื่อสร้างสำเนาของสตรีม: a List, array และ a Stream.Builder. ฉันเขียนโปรแกรมเปรียบเทียบเล็กน้อยซึ่งแนะนำว่าจากมุมมองด้านประสิทธิภาพListนั้นช้ากว่าอีกสองรายการประมาณ 30% ซึ่งค่อนข้างใกล้เคียงกัน

ข้อเสียเปรียบเพียงประการเดียวของการแปลงเป็นอาร์เรย์คือมันยุ่งยากหากประเภทองค์ประกอบของคุณเป็นประเภททั่วไป (ซึ่งในกรณีของฉันคือ) ดังนั้นฉันจึงชอบใช้ไฟล์Stream.Builder.

ฉันลงเอยด้วยการเขียนฟังก์ชันเล็กน้อยที่สร้างCollector:

private static <T> Collector<T, Stream.Builder<T>, Stream<T>> copyCollector()
{
    return Collector.of(Stream::builder, Stream.Builder::add, (b1, b2) -> {
        b2.build().forEach(b1);
        return b1;
    }, Stream.Builder::build);
}

จากนั้นฉันสามารถทำสำเนาสตรีมใดก็ได้strโดยการทำstr.collect(copyCollector())ที่ให้ความรู้สึกสอดคล้องกับการใช้งานสตรีมที่เป็นสำนวน

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