Cara Break dari Java Stream forEach

1. Gambaran keseluruhan

Sebagai pembangun Java, kita sering menulis kod yang berulang pada sekumpulan elemen dan melakukan operasi pada setiap elemen. Pustaka aliran Java 8 dan kaedah forEach membolehkan kami menulis kod tersebut dengan cara yang jelas dan jelas.

Walaupun ini serupa dengan gelung, kita tidak ada yang sama dengan pernyataan putus untuk membatalkan lelaran . Aliran boleh menjadi sangat panjang, atau berpotensi tidak terbatas , dan jika kita tidak mempunyai alasan untuk terus memprosesnya, kami ingin melepaskannya, daripada menunggu elemen terakhirnya.

Dalam tutorial ini, kita akan melihat beberapa mekanisme yang membolehkan kita mensimulasikan pernyataan putus pada operasi Stream.forEach .

2. Java 9's Stream.takeWhile ()

Anggaplah kita mempunyai aliran item String dan kita ingin memproses elemennya selagi panjangnya ganjil.

Mari cuba kaedah Java 9 Stream.takeWhile :

Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck") .takeWhile(n -> n.length() % 2 != 0) .forEach(System.out::println);

Sekiranya kita menjalankannya, kita mendapat output:

cat dog

Mari bandingkan ini dengan kod setara di Java biasa menggunakan pernyataan untuk gelung dan putus , untuk membantu kami melihat cara kerjanya:

List list = asList("cat", "dog", "elephant", "fox", "rabbit", "duck"); for (int i = 0; i < list.size(); i++) { String item = list.get(i); if (item.length() % 2 == 0) { break; } System.out.println(item); } 

Seperti yang kita lihat, kaedah takeWhile membolehkan kita mencapai apa yang kita perlukan.

Tetapi bagaimana jika kita belum menerapkan Java 9? Bagaimana kita dapat mencapai sesuatu yang serupa dengan menggunakan Java 8?

3. Pemisah Khas

Mari buat Spliterator tersuai yang akan berfungsi sebagai penghias untuk Stream.spliterator . Kita boleh menjadikan Spliterator ini melakukan rehat untuk kita.

Pertama, kami akan mendapatkan Spliterator dari aliran kami, kemudian kami menghiasnya dengan CustomSpliterator kami dan menyediakan Predicate untuk mengawal operasi rehat . Akhirnya, kami akan membuat aliran baru dari CustomSpliterator:

public static  Stream takeWhile(Stream stream, Predicate predicate) { CustomSpliterator customSpliterator = new CustomSpliterator(stream.spliterator(), predicate); return StreamSupport.stream(customSpliterator, false); }

Mari lihat bagaimana membuat CustomSpliterator :

public class CustomSpliterator extends Spliterators.AbstractSpliterator { private Spliterator splitr; private Predicate predicate; private boolean isMatched = true; public CustomSpliterator(Spliterator splitr, Predicate predicate) { super(splitr.estimateSize(), 0); this.splitr = splitr; this.predicate = predicate; } @Override public synchronized boolean tryAdvance(Consumer consumer) { boolean hadNext = splitr.tryAdvance(elem -> { if (predicate.test(elem) && isMatched) { consumer.accept(elem); } else { isMatched = false; } }); return hadNext && isMatched; } }

Oleh itu, mari kita lihat kaedah tryAdvance . Kita dapat lihat di sini bahawa Spliterator khusus memproses elemen Spliterator yang dihiasi . Pemprosesan dilakukan selagi predikat kami dipadankan dan aliran awal masih mempunyai unsur. Apabila salah satu syarat menjadi salah , Spliterator kami "pecah" dan operasi streaming berakhir.

Mari uji kaedah pembantu baru kami:

@Test public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() { Stream initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck"); List result = CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0) .collect(Collectors.toList()); assertEquals(asList("cat", "dog"), result); }

Seperti yang kita lihat, arus berhenti setelah keadaan itu dipenuhi. Untuk tujuan ujian, kami telah mengumpul hasil ke dalam senarai, tetapi kita juga boleh menggunakan foreach panggilan atau apa-apa fungsi lain Stream .

4. Custom foreach

Walaupun menyediakan aliran dengan mekanisme istirahat yang tertanam dapat berguna, mungkin lebih mudah untuk fokus hanya pada operasi forEach .

Mari gunakan Stream.spliterator secara langsung tanpa penghias:

public class CustomForEach { public static class Breaker { private boolean shouldBreak = false; public void stop() { shouldBreak = true; } boolean get() { return shouldBreak; } } public static  void forEach(Stream stream, BiConsumer consumer) { Spliterator spliterator = stream.spliterator(); boolean hadNext = true; Breaker breaker = new Breaker(); while (hadNext && !breaker.get()) { hadNext = spliterator.tryAdvance(elem -> { consumer.accept(elem, breaker); }); } } }

Seperti yang kita dapat lihat, adat baru foreach kaedah panggilan BiConsumer menyediakan kod kami dengan kedua-dua elemen seterusnya dan objek pemutus ia boleh digunakan untuk menghentikan aliran.

Mari cuba ini dalam ujian unit:

@Test public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() { Stream initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck"); List result = new ArrayList(); CustomForEach.forEach(initialStream, (elem, breaker) -> { if (elem.length() % 2 == 0) { breaker.stop(); } else { result.add(elem); } }); assertEquals(asList("cat", "dog"), result); }

5. Kesimpulan

Dalam artikel ini, kami melihat cara untuk memberikan setara dengan panggilan rehat di aliran. Kami melihat bagaimana pengambilan Java 9 Sementara menyelesaikan sebahagian besar masalah bagi kami dan bagaimana menyediakan versi untuk Java 8.

Akhirnya, kami melihat kaedah utiliti yang dapat memberi kami setara dengan operasi rehat semasa melakukan lelaran pada Aliran .

Seperti biasa, kod contoh boleh didapati di GitHub.