Cara Memutus Aliran Java forEach

1. Ikhtisar

Sebagai pengembang Java, kami sering menulis kode yang mengulangi sekumpulan elemen dan melakukan operasi pada masing-masing elemen. Pustaka aliran Java 8 dan metode forEach -nya memungkinkan kita untuk menulis kode itu dengan cara yang bersih dan deklaratif.

Meskipun ini mirip dengan loop, kami kehilangan padanan pernyataan break untuk membatalkan iterasi . Aliran bisa sangat panjang, atau berpotensi tidak terbatas , dan jika kami tidak memiliki alasan untuk melanjutkan pemrosesan, kami ingin memutuskannya, daripada menunggu elemen terakhirnya.

Dalam tutorial ini, kita akan melihat beberapa mekanisme yang memungkinkan kita untuk mensimulasikan pernyataan break pada operasi Stream.forEach .

2. Stream.takeWhile () Java 9

Misalkan kita memiliki aliran item String dan kita ingin memproses elemennya selama panjangnya ganjil.

Mari kita coba metode Stream.takeWhile Java 9 :

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

Jika kami menjalankan ini, kami mendapatkan output:

cat dog

Mari bandingkan ini dengan kode yang setara di Java biasa menggunakan pernyataan for loop dan break , 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 bisa kita lihat, metode takeWhile memungkinkan kita mencapai apa yang kita butuhkan.

Tapi bagaimana jika kita belum mengadopsi Java 9? Bagaimana kita bisa mencapai hal serupa menggunakan Java 8?

3. Sebuah Spliterator Kustom

Mari buat Spliterator khusus yang akan berfungsi sebagai dekorator untuk Stream.spliterator . Kita bisa membuat Spliterator ini melakukan break untuk kita.

Pertama, kita akan mendapatkan Spliterator dari aliran kita, lalu kita akan menghiasnya dengan CustomSpliterator kita dan memberikan Predikat untuk mengontrol operasi pemutusan hubungan kerja. Terakhir, 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 kita lihat cara 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; } }

Jadi, mari kita lihat metode tryAdvance . Kita dapat melihat di sini bahwa Spliterator kustom memproses elemen Spliterator yang dihias . Pemrosesan dilakukan selama predikat kita cocok dan aliran awal masih memiliki elemen. Ketika salah satu kondisi menjadi salah , Spliterator kami "rusak" dan operasi streaming berakhir.

Mari kita uji metode pembantu baru kita:

@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 bisa kita lihat, aliran berhenti setelah kondisi terpenuhi. Untuk tujuan pengujian, kami telah mengumpulkan hasil ke dalam daftar, tetapi kami juga dapat menggunakan panggilan forEach atau fungsi Stream lainnya .

4. Sebuah Custom forEach

Meskipun menyediakan Stream dengan mekanisme break yang disematkan dapat berguna, mungkin lebih mudah untuk fokus hanya pada operasi forEach .

Mari gunakan Stream.spliterator secara langsung tanpa dekorator:

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 bisa kita lihat, metode forEach khusus yang baru memanggil BiConsumer yang menyediakan kode kita dengan elemen berikutnya dan objek pemecah yang dapat digunakan untuk menghentikan streaming.

Mari kita coba ini dalam pengujian 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 yang setara dengan pemanggilan jeda di aliran. Kami melihat bagaimana takeWhile Java 9 memecahkan sebagian besar masalah untuk kami dan cara menyediakan versinya untuk Java 8.

Terakhir, kami melihat metode utilitas yang dapat memberi kami operasi break yang setara saat melakukan iterasi pada Stream .

Seperti biasa, kode contoh dapat ditemukan di GitHub.