Bagaimana cara memeriksa apakah a Stream
kosong dan mengeluarkan pengecualian jika tidak, sebagai operasi non-terminal?
Pada dasarnya, saya mencari sesuatu yang setara dengan kode di bawah ini, tetapi tanpa mewujudkan aliran di antaranya. Secara khusus, pemeriksaan tidak boleh dilakukan sebelum aliran benar-benar dikonsumsi oleh operasi terminal.
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();
}
java
java-8
java-stream
Cephalopoda
sumber
sumber
Jawaban:
Jika Anda dapat hidup dengan kapabilitas paralel terbatas, solusi berikut akan berfungsi:
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); }
Berikut beberapa contoh kode yang menggunakannya:
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);
Masalah dengan eksekusi paralel (efisien) adalah bahwa mendukung pemisahan
Spliterator
memerlukan cara yang aman untuk thread untuk mengetahui apakah salah satu fragmen telah melihat nilai dengan cara yang aman untuk thread. Kemudian fragmen terakhir yang dieksekusitryAdvance
harus menyadari bahwa itu adalah yang terakhir (dan juga tidak bisa maju) untuk mengeluarkan pengecualian yang sesuai. Jadi saya tidak menambahkan dukungan untuk pemisahan di sini.sumber
Jawaban dan komentar lain benar karena untuk memeriksa konten aliran, seseorang harus menambahkan operasi terminal, dengan demikian "menghabiskan" aliran. Namun, seseorang dapat melakukan ini dan mengubah hasilnya kembali menjadi aliran, tanpa menyangga seluruh konten aliran. Berikut ini beberapa contoh:
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()); } }
Pada dasarnya ubah aliran menjadi an
Iterator
untuk memanggilnyahasNext()
, dan jika benar, ubahIterator
kembali menjadi aStream
. Ini tidak efisien karena semua operasi selanjutnya di aliran akan melalui IteratorhasNext()
dannext()
metode , yang juga menyiratkan bahwa aliran diproses secara efektif secara berurutan (bahkan jika kemudian berubah paralel). Namun, ini memungkinkan Anda untuk menguji streaming tanpa membuat buffer ke semua elemennya.Mungkin ada cara untuk melakukan ini menggunakan
Spliterator
bukan sebuahIterator
. Hal ini berpotensi memungkinkan aliran yang dikembalikan memiliki karakteristik yang sama dengan aliran masukan, termasuk berjalan secara paralel.sumber
estimatedSize
dancharacteristics
bahkan dapat meningkatkan kinerja single-threaded. Kebetulan saya menulisSpliterator
solusi saat Anda mempostingIterator
solusi…tryAdvance
sebelumStream
melakukannya akan mengubah sifat malasStream
menjadi aliran "malas sebagian". Ini juga menyiratkan bahwa mencari elemen pertama bukanlah operasi paralel lagi karena Anda harus membelah terlebih dahulu dan melakukantryAdvance
pada bagian yang terbelah secara bersamaan untuk melakukan operasi paralel yang sebenarnya, sejauh yang saya mengerti. Jika satu-satunya operasi terminal adalahfindAny
atau serupa itu akan menghancurkan seluruhparallel()
permintaan.tryAdvance
sebelum streaming dan harus menggabungkan setiap bagian yang terpisah menjadi proxy dan mengumpulkan informasi "hasAny" dari semua operasi bersamaan Anda sendiri dan memastikan bahwa operasi bersamaan terakhir menampilkan pengecualian yang diinginkan jika aliran kosong. Banyak hal…Ini mungkin cukup dalam banyak kasus
sumber
Anda harus melakukan operasi terminal di Arus agar salah satu filter dapat diterapkan. Oleh karena itu Anda tidak dapat mengetahui apakah itu akan kosong sampai Anda mengkonsumsinya.
Hal terbaik yang dapat Anda lakukan adalah menghentikan Stream dengan file
findAny()
operasi terminal, yang akan berhenti ketika menemukan elemen apa pun, tetapi jika tidak ada, ia harus mengulang semua daftar input untuk mengetahuinya.Ini hanya akan membantu Anda jika daftar input memiliki banyak elemen, dan salah satu dari beberapa yang pertama melewati filter, karena hanya sebagian kecil dari daftar yang harus dipakai sebelum Anda mengetahui Stream tidak kosong.
Tentu saja Anda masih harus membuat Stream baru untuk menghasilkan daftar output.
sumber
anyMatch(alwaysTrue())
, saya pikir itu yang paling dekathasAny
.anyMatch(alwaysTrue())
sangat cocok dengan semantik yang Anda inginkanhasAny
, memberi Andaboolean
alih - alihOptional<T>
--- tetapi kami membagi rambut di sini :)alwaysTrue
adalah predikat Jambu Biji.anyMatch(e -> true)
kemudian.Saya pikir harus cukup untuk memetakan boolean
Dalam kode ini adalah:
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
sumber
Stream.anyMatch()
Mengikuti ide Stuart, ini bisa dilakukan dengan cara
Spliterator
seperti ini: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; } }
Saya pikir ini berfungsi dengan Stream paralel karena
stream.spliterator()
operasi akan menghentikan streaming, dan kemudian membangunnya kembali sesuai kebutuhanDalam kasus penggunaan saya, saya membutuhkan default
Stream
daripada nilai default. itu cukup mudah untuk diubah jika bukan ini yang Anda butuhkansumber
Spliterator
saya bertanya-tanya bagaimana keduanya dibandingkan.Saya hanya akan menggunakan:
stream.count()>0
sumber