Hentikan atau kembali dari aliran Java 8 forEach?

313

Saat menggunakan iterasi eksternal di atas Iterablekami menggunakan breakatau returndari peningkatan untuk-setiap loop sebagai:

for (SomeObject obj : someObjects) {
   if (some_condition_met) {
      break; // or return obj
   }
}

Bagaimana kita bisa breakatau returnmenggunakan iterasi internal dalam ekspresi Java 8 lambda seperti:

someObjects.forEach(obj -> {
   //what to do here?
})
Tapas Bose
sumber
6
Kamu tidak bisa Cukup gunakan forpernyataan nyata .
Boann
1
kemungkinan duplikat Java 8: Batasi aliran tanpa batas dengan predikat
assylias
Tentu saja adalah mungkin bagi forEach(). Solusinya adalah tidak baik, tetapi adalah mungkin. Lihat jawaban saya di bawah ini.
Honza Zidek
Pertimbangkan pendekatan lain, Anda hanya ingin tidak mengeksekusi kode , jadi, ifkondisi sederhana di dalam forEachakan melakukan trik.
Thomas Decaux

Jawaban:

374

Jika Anda membutuhkan ini, Anda tidak boleh menggunakan forEach, tetapi salah satu metode lain yang tersedia di stream; yang mana, tergantung pada apa tujuan Anda.

Misalnya, jika tujuan dari loop ini adalah untuk menemukan elemen pertama yang cocok dengan beberapa predikat:

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findFirst();

(Catatan: Ini tidak akan mengulangi seluruh koleksi, karena aliran dievaluasi malas - itu akan berhenti di objek pertama yang cocok dengan kondisi).

Jika Anda hanya ingin tahu apakah ada elemen dalam koleksi yang kondisinya benar, Anda bisa menggunakan anyMatch:

boolean result = someObjects.stream().anyMatch(obj -> some_condition_met);
Jesper
sumber
7
Ini berfungsi jika menemukan objek adalah tujuan, tetapi tujuan itu biasanya dilayani oleh returnpernyataan dari suatu findSomethingmetode. breaklebih biasanya dikaitkan dengan take -jenis operasi.
Marko Topolnik
1
@MarkoTopolnik Ya, poster asli belum memberi kami informasi yang cukup untuk mengetahui apa sebenarnya tujuannya; "luangkan waktu" adalah kemungkinan ketiga selain dua yang saya sebutkan. (Apakah ada cara sederhana untuk melakukan "take while" dengan stream?).
Jesper
3
Bagaimana dengan kapan tujuannya adalah menerapkan perilaku batal dengan benar? Apakah hal terbaik yang bisa kita lakukan hanya dengan melempar pengecualian runtime di dalam lambda forEach ketika kita melihat bahwa pengguna meminta pembatalan?
user2163960
1
@HonzaZidek Diedit, tetapi intinya bukan apakah itu mungkin atau tidak, tetapi apa cara yang benar untuk melakukan sesuatu. Anda seharusnya tidak mencoba memaksa menggunakan forEachuntuk ini; Anda harus menggunakan metode lain yang lebih tepat.
Jesper
1
@Jesper Saya setuju dengan Anda, saya menulis bahwa saya tidak suka "Solusi pengecualian". Namun kata-kata Anda "Ini tidak mungkin dengan forEach" secara teknis salah. Saya juga lebih suka solusi Anda, namun saya bisa membayangkan menggunakan kasus-kasus di mana solusi yang disediakan dalam jawaban saya lebih disukai: ketika loop harus diakhiri karena pengecualian nyata . Saya setuju bahwa Anda seharusnya tidak secara umum menggunakan pengecualian untuk mengontrol arus.
Honza Zidek
54

Ini adalah mungkin bagi Iterable.forEach()(tetapi tidak andal dengan Stream.forEach()). Solusinya adalah tidak baik, tetapi adalah mungkin.

PERINGATAN : Anda tidak boleh menggunakannya untuk mengendalikan logika bisnis, tetapi murni untuk menangani situasi luar biasa yang terjadi selama eksekusi forEach(). Seperti sumber daya tiba-tiba berhenti diakses, salah satu objek yang diproses melanggar kontrak (mis. Kontrak mengatakan bahwa semua elemen dalam aliran tidak boleh nulltiba-tiba dan tidak terduga salah satunya null) dll.

Menurut dokumentasi untuk Iterable.forEach():

Lakukan tindakan yang diberikan untuk setiap elemen Iterable hingga semua elemen diproses atau tindakan melempar pengecualian ... Pengecualian yang dilemparkan oleh tindakan diteruskan ke pemanggil.

Jadi Anda melempar pengecualian yang akan segera memecah loop internal.

Kode akan menjadi seperti ini - saya tidak bisa mengatakan saya menyukainya tetapi berfungsi. Anda membuat kelas Anda sendiri BreakExceptionyang memanjang RuntimeException.

try {
    someObjects.forEach(obj -> {
        // some useful code here
        if(some_exceptional_condition_met) {
            throw new BreakException();
       }
    }
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}

Perhatikan bahwa try...catchadalah tidak sekitar ekspresi lambda, melainkan sekitar seluruh forEach()metode. Untuk membuatnya lebih terlihat, lihat transkripsi kode berikut yang menunjukkan lebih jelas:

Consumer<? super SomeObject> action = obj -> {
    // some useful code here
    if(some_exceptional_condition_met) {
        throw new BreakException();
    }
});

try {
    someObjects.forEach(action);
}
catch (BreakException e) {
    // here you know that your condition has been met at least once
}
Honza Zidek
sumber
37
Saya pikir ini adalah praktik yang buruk dan tidak boleh dianggap sebagai solusi untuk masalah ini. Ini berbahaya karena bisa menyesatkan bagi pemula. Menurut Effective Java 2nd Edition, Bab 9, Item 57: 'Gunakan pengecualian hanya untuk kondisi luar biasa'. Selanjutnya 'Gunakan pengecualian runtime untuk menunjukkan kesalahan pemrograman'. Secara definitif, saya sangat menganjurkan siapa pun mempertimbangkan solusi ini untuk melihat ke dalam solusi @Jesper.
Louis F.
15
@LouisF. Saya secara eksplisit mengatakan, "Saya tidak bisa mengatakan saya suka tapi berhasil" OP bertanya "bagaimana cara keluar dari forEach ()" dan ini adalah jawabannya. Saya sepenuhnya setuju bahwa ini tidak boleh digunakan untuk mengendalikan logika bisnis. Namun saya dapat membayangkan beberapa kasus penggunaan yang bermanfaat, seperti itu koneksi ke sumber daya tiba-tiba tidak tersedia di tengah forEach () atau lebih, yang menggunakan pengecualian bukanlah praktik yang buruk. Saya telah menambahkan paragraf untuk jawaban saya agar lebih jelas.
Honza Zidek
1
Saya pikir ini adalah solusi yang bagus. Setelah mencari di Google untuk "pengecualian java" dan pencarian lainnya dengan beberapa kata lagi seperti "praktik terbaik" atau "tidak dicentang", dll., Saya melihat ada kontroversi mengenai cara menggunakan pengecualian. Saya menggunakan solusi ini dalam kode saya karena aliran sedang melakukan peta yang akan memakan waktu beberapa menit. Saya ingin pengguna untuk dapat membatalkan tugas jadi saya memeriksa di awal setiap perhitungan untuk bendera "isUserCancelRequested" dan melemparkan pengecualian ketika benar. Ini bersih, kode pengecualian diisolasi untuk sebagian kecil dari kode, dan berfungsi.
Jason
2
Perhatikan bahwa Stream.forEachtidak tidak memberikan jaminan yang kuat yang sama tentang pengecualian yang disampaikan ke pemanggil, sehingga melemparkan pengecualian tidak dijamin untuk bekerja dengan cara ini untuk Stream.forEach.
Radiodef
1
@Radiodef Itu adalah poin yang valid, terima kasih. Posting asli tentang Iterable.forEach(), tapi saya menambahkan poin Anda ke teks saya hanya untuk kelengkapan.
Honza Zidek
43

Pengembalian dalam lambda sama dengan melanjutkan di masing-masing, tetapi tidak ada yang setara dengan istirahat. Anda bisa melakukan pengembalian untuk melanjutkan:

someObjects.forEach(obj -> {
   if (some_condition_met) {
      return;
   }
})
Aneesh Vijendran
sumber
2
Solusi yang bagus dan idiomatis untuk memenuhi persyaratan umum. Jawaban yang diterima memperkirakan persyaratan.
davidxxx
6
dengan kata lain ini tidak "Istirahat atau kembali dari Java 8 stream forEach" yang merupakan pertanyaan sebenarnya
Jaroslav Záruba
1
Ini masih akan "menarik" catatan melalui aliran sumber, yang buruk jika Anda melakukan paging melalui semacam dataset jarak jauh.
Adrian Baker
23

Di bawah ini Anda menemukan solusi yang saya gunakan dalam sebuah proyek. Sebaliknya forEachgunakan saja allMatch:

someObjects.allMatch(obj -> {
    return !some_condition_met;
});
Julian Pieles
sumber
11

Entah Anda perlu menggunakan metode yang menggunakan predikat yang menunjukkan apakah akan terus berjalan (jadi malah ada istirahat) atau Anda perlu melempar pengecualian - yang tentu saja merupakan pendekatan yang sangat jelek.

Jadi Anda bisa menulis forEachConditionalmetode seperti ini:

public static <T> void forEachConditional(Iterable<T> source,
                                          Predicate<T> action) {
    for (T item : source) {
        if (!action.test(item)) {
            break;
        }
    }
}

Daripada Predicate<T>, Anda mungkin ingin mendefinisikan antarmuka fungsional Anda sendiri dengan metode umum yang sama (sesuatu mengambil Tdan mengembalikan a bool) tetapi dengan nama yang menunjukkan harapan lebih jelas - Predicate<T>tidak ideal di sini.

Jon Skeet
sumber
1
Saya menyarankan agar benar-benar menggunakan Streams API dan pendekatan fungsional di sini lebih disukai daripada membuat metode penolong ini jika Java 8 digunakan bagaimanapun.
skiwi
3
Ini adalah takeWhileoperasi klasik , dan pertanyaan ini hanyalah salah satu dari yang menunjukkan seberapa besar kekurangannya dalam API Streams.
Marko Topolnik
2
@ Marko: takeWhile merasa lebih seperti itu akan menjadi operasi menghasilkan item, tidak melakukan aksi pada masing-masing. Tentu saja dalam LINQ di .NET akan menjadi bentuk yang buruk untuk menggunakan TakeWhile dengan tindakan dengan efek samping.
Jon Skeet
9

Anda dapat menggunakan java8 + rxjava .

//import java.util.stream.IntStream;
//import rx.Observable;

    IntStream intStream  = IntStream.range(1,10000000);
    Observable.from(() -> intStream.iterator())
            .takeWhile(n -> n < 10)
            .forEach(n-> System.out.println(n));
frhack
sumber
8
Java 9 akan menawarkan dukungan untuk operasi takeWhile di stream.
pisaruk
6

Untuk kinerja maksimal dalam operasi paralel gunakan findAny () yang mirip dengan findFirst ().

Optional<SomeObject> result =
    someObjects.stream().filter(obj -> some_condition_met).findAny();

Namun Jika hasil yang stabil diinginkan, gunakan findFirst () sebagai gantinya.

Perhatikan juga bahwa pola yang cocok (anyMatch () / allMatch) hanya akan mengembalikan boolean, Anda tidak akan mendapatkan objek yang cocok.

Kanagavelu Sugumar
sumber
6

Perbarui dengan Java 9+ dengan takeWhile:

MutableBoolean ongoing = MutableBoolean.of(true);
someobjects.stream()...takeWhile(t -> ongoing.value()).forEach(t -> {
    // doing something.
    if (...) { // want to break;
        ongoing.setFalse();
    }
});
user_3380739
sumber
3

Saya telah mencapai sesuatu seperti ini

  private void doSomething() {
            List<Action> actions = actionRepository.findAll();
            boolean actionHasFormFields = actions.stream().anyMatch(actionHasMyFieldsPredicate());
            if (actionHasFormFields){
                context.addError(someError);
            }
        }
    }

    private Predicate<Action> actionHasMyFieldsPredicate(){
        return action -> action.getMyField1() != null;
    }
Mohammad Adnan
sumber
3

Anda dapat mencapainya menggunakan campuran peek (..) dan anyMatch (..).

Menggunakan contoh Anda:

someObjects.stream().peek(obj -> {
   <your code here>
}).anyMatch(obj -> !<some_condition_met>);

Atau cukup tulis metode util generik:

public static <T> void streamWhile(Stream<T> stream, Predicate<? super T> predicate, Consumer<? super T> consumer) {
    stream.peek(consumer).anyMatch(predicate.negate());
}

Dan kemudian gunakan, seperti ini:

streamWhile(someObjects.stream(), obj -> <some_condition_met>, obj -> {
   <your code here>
});
tuga
sumber
0

Bagaimana dengan yang ini:

final BooleanWrapper condition = new BooleanWrapper();
someObjects.forEach(obj -> {
   if (condition.ok()) {
     // YOUR CODE to control
     condition.stop();
   }
});

Di mana BooleanWrapperkelas Anda harus menerapkan untuk mengontrol aliran.

Thomas Decaux
sumber
3
Atau AtomicBoolean?
Koekje
1
Ini melompati pemrosesan objek ketika !condition.ok(), namun itu tidak mencegah forEach()perulangan atas semua objek. Jika bagian yang lambat adalah forEach()iterasi dan bukan penggunanya (misalnya, ia mendapatkan objek dari koneksi jaringan yang lambat), maka pendekatan ini tidak terlalu berguna.
zakmck
Ya Anda benar, jawaban saya cukup salah dalam kasus ini.
Thomas Decaux
0
int valueToMatch = 7;
Stream.of(1,2,3,4,5,6,7,8).anyMatch(val->{
   boolean isMatch = val == valueToMatch;
   if(isMatch) {
      /*Do whatever you want...*/
       System.out.println(val);
   }
   return isMatch;
});

Ini hanya akan melakukan operasi di mana ia menemukan kecocokan, dan setelah menemukan kecocokan itu menghentikan iterasi.

Khairul Bashar Lemon
sumber