Saya mencoba menggunakan Java 8 Stream
s untuk menemukan elemen dalam aLinkedList
. Namun saya ingin menjamin bahwa hanya ada satu dan hanya satu yang cocok dengan kriteria filter.
Ambil kode ini:
public static void main(String[] args) {
LinkedList<User> users = new LinkedList<>();
users.add(new User(1, "User1"));
users.add(new User(2, "User2"));
users.add(new User(3, "User3"));
User match = users.stream().filter((user) -> user.getId() == 1).findAny().get();
System.out.println(match.toString());
}
static class User {
@Override
public String toString() {
return id + " - " + username;
}
int id;
String username;
public User() {
}
public User(int id, String username) {
this.id = id;
this.username = username;
}
public void setUsername(String username) {
this.username = username;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public int getId() {
return id;
}
}
Kode ini menemukan User
berdasarkan ID mereka. Tetapi tidak ada jaminan berapa banyakUser
yang cocok dengan filter.
Mengubah garis filter ke:
User match = users.stream().filter((user) -> user.getId() < 0).findAny().get();
Akan melempar NoSuchElementException
(bagus!)
Saya ingin melontarkan kesalahan jika ada beberapa pertandingan. Apakah ada cara untuk melakukan ini?
java
lambda
java-8
java-stream
ryvantage
sumber
sumber
count()
adalah operasi terminal sehingga Anda tidak dapat melakukan itu. Aliran tidak dapat digunakan setelah.Stream::size
?Stream
jauh lebih banyak daripada yang saya lakukan sebelumnya ...LinkedHashSet
(dengan asumsi Anda ingin agar penyisipan dipertahankan) atauHashSet
semuanya. Jika koleksi Anda hanya digunakan untuk menemukan id pengguna tunggal, lalu mengapa Anda mengumpulkan semua item lainnya? Jika ada potensi bahwa Anda akan selalu perlu menemukan beberapa id pengguna yang juga perlu unik, lalu mengapa menggunakan daftar dan bukan set? Anda memprogram mundur. Gunakan koleksi yang tepat untuk pekerjaan itu dan selamatkan diri Anda dari sakit kepala iniJawaban:
Buat custom
Collector
Kami menggunakan
Collectors.collectingAndThen
untuk membangun yang diinginkanCollector
olehList
denganCollectors.toList()
kolektor.IllegalStateException
iflist.size != 1
.Digunakan sebagai:
Anda kemudian dapat menyesuaikan ini
Collector
sebanyak yang Anda inginkan, misalnya memberikan pengecualian sebagai argumen dalam konstruktor, menyesuaikannya untuk memungkinkan dua nilai, dan banyak lagi.Alternatif - solusi yang bisa dibilang kurang elegan:
Anda dapat menggunakan 'solusi' yang melibatkan
peek()
danAtomicInteger
, tetapi sebenarnya Anda seharusnya tidak menggunakannya.Apa yang bisa Anda lakukan hanya mengumpulkan itu dalam
List
, seperti ini:sumber
Iterables.getOnlyElement
akan mempersingkat solusi ini dan memberikan pesan kesalahan yang lebih baik. Sama seperti tip untuk sesama pembaca yang sudah menggunakan Google Guava.singletonCollector()
definisi yang sudah usang oleh versi yang tetap ada di posting, dan mengganti namanya menjaditoSingleton()
. Keahlian streaming Java saya agak berkarat, tetapi penggantian nama terlihat membantu saya. Meninjau perubahan ini butuh waktu 2 menit, puncak. Jika Anda tidak punya waktu untuk meninjau hasil edit, dapatkah saya menyarankan Anda meminta orang lain untuk melakukan ini di masa depan, mungkin di ruang obrolan Java ?Demi kelengkapan, berikut adalah 'one-liner' yang sesuai dengan jawaban istimewa @ prunge:
Ini mendapatkan elemen pencocokan tunggal dari aliran, melempar
NoSuchElementException
dalam kasus aliran kosong, atauIllegalStateException
dalam hal aliran mengandung lebih dari satu elemen yang cocok.Variasi dari pendekatan ini menghindari melemparkan pengecualian lebih awal dan sebagai gantinya menyatakan hasilnya sebagai
Optional
mengandung elemen tunggal, atau tidak ada (kosong) jika ada nol atau beberapa elemen:sumber
get()
keorElseThrow()
Jawaban lain yang melibatkan penulisan kebiasaan
Collector
mungkin lebih efisien (seperti jawaban Louis Wasserman , +1), tetapi jika Anda ingin singkatnya, saya sarankan yang berikut:Kemudian verifikasi ukuran daftar hasil.
sumber
limit(2)
solusi ini? Apa bedanya jika daftar yang dihasilkan adalah 2 atau 100? Jika lebih besar dari 1.Collectors.collectingAndThen(toList(), l -> { if (l.size() == 1) return l.get(0); throw new RuntimeException(); })
maxSize: the number of elements the stream should be limited to
. Jadi, tidak harus itu.limit(1)
bukan.limit(2)
?result.size()
untuk memastikan sama dengan 1. Jika 2, maka ada lebih dari satu kecocokan, jadi ini merupakan kesalahan. Jika bukan kode yang cocoklimit(1)
, lebih dari satu kecocokan akan menghasilkan satu elemen, yang tidak dapat dibedakan dari hanya ada satu kecocokan. Ini akan melewatkan kasus kesalahan yang dikhawatirkan OP.Jambu memberikan
MoreCollectors.onlyElement()
yang melakukan hal yang benar di sini. Tetapi jika Anda harus melakukannya sendiri, Anda bisa melakukannya sendiriCollector
:... atau menggunakan
Holder
tipe Anda sendiri, bukanAtomicReference
. Anda dapat menggunakannya kembaliCollector
sebanyak yang Anda suka.sumber
Collector
adalah cara untuk pergi.List
lebih mahal daripada satu referensi yang bisa berubah.MoreCollectors.onlyElement()
seharusnya benar-benar menjadi yang pertama (dan mungkin satu-satunya :))Gunakan Guava's
MoreCollectors.onlyElement()
( JavaDoc ).Itu melakukan apa yang Anda inginkan dan melempar
IllegalArgumentException
jika aliran terdiri dari dua atau lebih elemen, danNoSuchElementException
jika aliran kosong.Pemakaian:
sumber
MoreCollectors
adalah bagian dari versi yang belum dirilis (pada 2016-12) versi yang belum dirilis 21.Operasi "escape hatch" yang memungkinkan Anda melakukan hal-hal aneh yang tidak didukung oleh stream adalah meminta
Iterator
:Jambu biji memiliki metode kenyamanan untuk mengambil
Iterator
dan mendapatkan satu-satunya elemen, melempar jika ada nol atau beberapa elemen, yang bisa menggantikan garis n-1 bawah di sini.sumber
Memperbarui
Saran bagus dalam komentar dari @ Holger:
Jawaban asli
Pengecualian dilemparkan oleh
Optional#get
, tetapi jika Anda memiliki lebih dari satu elemen yang tidak akan membantu. Anda dapat mengumpulkan pengguna dalam koleksi yang hanya menerima satu item, misalnya:yang melempar
java.lang.IllegalStateException: Queue full
, tapi itu terasa terlalu gila.Atau Anda dapat menggunakan pengurangan yang dikombinasikan dengan opsional:
Pengurangan pada dasarnya mengembalikan:
Hasilnya kemudian dibungkus dalam opsional.
Tetapi solusi paling sederhana mungkin hanya dengan mengumpulkan ke koleksi, periksa bahwa ukurannya adalah 1 dan dapatkan satu-satunya elemen.
sumber
null
) untuk mencegah penggunaanget()
. Sayangnya Andareduce
tidak bekerja seperti yang Anda pikirkan, pertimbangkanStream
yang memilikinull
elemen di dalamnya, mungkin Anda berpikir bahwa Anda menutupinya, tapi saya bisa[User#1, null, User#2, null, User#3]
, sekarang tidak akan membuang pengecualian saya pikir, kecuali saya salah di sini.null
ke fungsi pengurangan, menghapus nilai identitas argumen akan membuat seluruh berurusan dengannull
dalam fungsi usang:reduce( (u,v) -> { throw new IllegalStateException("More than one ID found"); } )
melakukan pekerjaan dan bahkan lebih baik, itu sudah mengembalikanOptional
, eliding keharusan untuk memanggilOptional.ofNullable
pada hasil.Alternatifnya adalah menggunakan reduksi: (contoh ini menggunakan string tetapi bisa dengan mudah diterapkan ke semua jenis objek termasuk
User
)Jadi untuk kasus dengan
User
Anda akan memiliki:sumber
Menggunakan mengurangi
Ini adalah cara sederhana dan fleksibel yang saya temukan (berdasarkan jawaban @prunge)
Dengan cara ini Anda memperoleh:
Optional.empty()
jika tidak adasumber
Saya pikir cara ini lebih sederhana:
sumber
Menggunakan
Collector
:Pemakaian:
Kami mengembalikan sebuah
Optional
, karena kami biasanya tidak dapat menganggapCollection
mengandung persis satu elemen. Jika Anda sudah tahu ini masalahnya, hubungi:Ini menempatkan beban menangani kesalahan pada penelepon - sebagaimana mestinya.
sumber
Jambu memiliki
Collector
untuk ini disebutMoreCollectors.onlyElement()
.sumber
Kita dapat menggunakan RxJava ( perpustakaan ekstensi reaktif yang sangat kuat )
The tunggal Operator melempar pengecualian jika tidak ada pengguna atau lebih dari satu pengguna ditemukan.
sumber
Karena
Collectors.toMap(keyMapper, valueMapper)
menggunakan merger pelemparan untuk menangani banyak entri dengan kunci yang sama, mudah:Anda akan mendapatkan
IllegalStateException
kunci duplikat. Tetapi pada akhirnya saya tidak yakin apakah kodenya tidak akan lebih mudah dibaca menggunakanif
.sumber
.collect(Collectors.toMap(user -> "", Function.identity())).get("")
, Anda memiliki perilaku yang lebih umum.Saya menggunakan dua pengumpul tersebut:
sumber
onlyOne()
melemparIllegalStateException
untuk> 1 elemen, dan NoSuchElementException` (dalamOptional::get
) untuk 0 elemen.Supplier
dari(Runtime)Exception
.Jika Anda tidak keberatan menggunakan perpustakaan pihak ke-3,
SequenceM
dari cyclops-stream (danLazyFutureStream
dari reaksi sederhana ), keduanya memiliki operator tunggal & tunggal.singleOptional()
melempar pengecualian jika ada0
atau lebih dari1
elemen dalamStream
, jika tidak mengembalikan nilai tunggal.singleOptional()
mengembalikanOptional.empty()
jika tidak ada nilai atau lebih dari satu nilai diStream
.Pengungkapan - Saya adalah penulis dari kedua perpustakaan.
sumber
Saya pergi dengan pendekatan langsung dan hanya mengimplementasikannya:
dengan tes JUnit:
Implementasi ini bukan threadsafe.
sumber
sumber
Sudahkah Anda mencoba ini?
Sumber: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
sumber
count()
itu tidak baik untuk digunakan karena ini adalah operasi terminal.