Diberikan daftar elemen, saya ingin mendapatkan elemen dengan properti tertentu dan menghapusnya dari daftar. Solusi terbaik yang saya temukan adalah:
ProducerDTO p = producersProcedureActive
.stream()
.filter(producer -> producer.getPod().equals(pod))
.findFirst()
.get();
producersProcedureActive.remove(p);
Apakah mungkin untuk menggabungkan get dan remove dalam ekspresi lambda?
java
lambda
java-8
java-stream
Marco Stramezzi
sumber
sumber
get()
sini! Anda tidak tahu apakah itu kosong atau tidak. Anda akan membuat pengecualian jika elemen tidak ada di sana. Sebaliknya, gunakan salah satu metode aman seperti ifPresent, orElse, orElseGet, atau orElseThrow.list
dalamnyaPredicate
benar atau hanya yang pertama (dari kemungkinan nol, satu atau banyak elemen)?Jawaban:
Untuk menghapus elemen dari daftar
misalnya:
objectA.removeIf(x -> blockedWorkerIds.contains(x)); List<String> str1 = new ArrayList<String>(); str1.add("A"); str1.add("B"); str1.add("C"); str1.add("D"); List<String> str2 = new ArrayList<String>(); str2.add("D"); str2.add("E"); str1.removeIf(x -> str2.contains(x)); str1.forEach(System.out::println);
OUTPUT: A B C
sumber
removeIf
adalah solusi elegan untuk menghilangkan elemen dari koleksi, tetapi tidak mengembalikan elemen yang dihapus.Meskipun utasnya cukup tua, masih dianggap memberikan solusi - penggunaan
Java8
.Manfaatkan
removeIf
fungsi. Kompleksitas waktu adalahO(n)
Referensi API: removeIf docs
Asumsi:
producersProcedureActive
adalah aList
CATATAN: Dengan pendekatan ini Anda tidak akan bisa mendapatkan item yang dihapus.
sumber
Pertimbangkan untuk menggunakan iterator java vanilla untuk melakukan tugas:
public static <T> T findAndRemoveFirst(Iterable<? extends T> collection, Predicate<? super T> test) { T value = null; for (Iterator<? extends T> it = collection.iterator(); it.hasNext();) if (test.test(value = it.next())) { it.remove(); return value; } return null; }
Keuntungan :
Iterable
bahkan tanpastream()
dukungan (setidaknya yang menerapkanremove()
di iteratornya) .Kekurangan :
Adapun
jawaban lain dengan jelas menunjukkan bahwa itu mungkin, tetapi Anda harus menyadari
ConcurrentModificationException
mungkin terlempar saat menghapus elemen dari daftar yang sedang diiterasisumber
remove()
metode yang membuang UOE. (Bukan yang untuk koleksi JDK, tentu saja, tapi menurut saya tidak adil untuk mengatakan "bekerja pada semua Iterable".)default
implementasiremoveIf
membuat asumsi yang sama, tetapi, tentu saja, itu didefinisikan padaCollection
daripadaIterable
...Solusi langsungnya adalah dengan mengaktifkan
ifPresent(consumer)
Opsional yang dikembalikan olehfindFirst()
. Konsumen ini akan dipanggil jika opsionalnya tidak kosong. Manfaatnya juga adalah ia tidak akan memunculkan pengecualian jika operasi find mengembalikan opsional kosong, seperti yang akan dilakukan kode Anda saat ini; sebaliknya, tidak akan terjadi apa-apa.Jika Anda ingin mengembalikan nilai dihapus, Anda dapat
map
denganOptional
dengan hasil panggilanremove
:producersProcedureActive.stream() .filter(producer -> producer.getPod().equals(pod)) .findFirst() .map(p -> { producersProcedureActive.remove(p); return p; });
Tetapi perhatikan bahwa
remove(Object)
operasi tersebut akan melintasi daftar lagi untuk menemukan elemen yang akan dihapus. Jika Anda memiliki daftar dengan akses acak, seperti sebuahArrayList
, akan lebih baik untuk membuat Aliran di atas indeks daftar dan menemukan indeks pertama yang cocok dengan predikat:IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int) i));
Dengan solusi ini,
remove(int)
operasi beroperasi langsung pada indeks.sumber
LinkedList
Anda mungkin tidak boleh menggunakan API streaming karena tidak ada solusi tanpa melintasi setidaknya dua kali. Tapi saya tidak tahu skenario kehidupan nyata di mana keuntungan akademis dari daftar tertaut dapat mengimbangi overhead sebenarnya. Jadi solusi sederhananya adalah jangan pernah menggunakanLinkedList
.remove(Object)
hanya mengembalikan yangboolean
memberi tahu apakah ada elemen yang akan dihapus atau tidak.boxed()
Anda mendapatkanOptionalInt
yang hanya bisamap
dariint
sampaiint
. Tidak sepertiIntStream
, tidak adamapToObj
metode. Denganboxed()
, Anda akan mendapatkanOptional<Integer>
yang memungkinkan kemap
objek arbitrer, yaituProducerDTO
dikembalikan olehremove(int)
. Pemeran dariInteger
keint
perlu untuk membedakan antararemove(int)
danremove(Object)
.Gunakan dapat menggunakan filter Java 8, dan membuat daftar lain jika Anda tidak ingin mengubah daftar lama:
sumber
Saya yakin ini akan menjadi jawaban yang tidak populer, tetapi berhasil ...
ProducerDTO[] p = new ProducerDTO[1]; producersProcedureActive .stream() .filter(producer -> producer.getPod().equals(pod)) .findFirst() .ifPresent(producer -> {producersProcedureActive.remove(producer); p[0] = producer;}
p[0]
akan menahan elemen yang ditemukan atau menjadi nol."Trik" di sini adalah mengelak dari masalah "efektif akhir" dengan menggunakan referensi larik yang efektif final, tetapi menyetel elemen pertamanya.
sumber
.orElse(null)
untuk mendapatkanProducerDTO
ataunull
....orElse(null)
dan memilikiif
, bukan?remove()
juga dengan menggunakanorElse(null)
?if(p!=null) producersProcedureActive.remove(p);
itu masih lebih pendek dari ekspresi lambda dalamifPresent
panggilan Anda .Dengan Koleksi Eclipse, Anda dapat menggunakannya
detectIndex
bersamaremove(int)
di java.util.List apa pun.List<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5); int index = Iterate.detectIndex(integers, i -> i > 2); if (index > -1) { integers.remove(index); } Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);
Jika Anda menggunakan
MutableList
tipe dari Eclipse Collections, Anda dapat memanggildetectIndex
metode secara langsung di daftar.MutableList<Integer> integers = Lists.mutable.with(1, 2, 3, 4, 5); int index = integers.detectIndex(i -> i > 2); if (index > -1) { integers.remove(index); } Assert.assertEquals(Lists.mutable.with(1, 2, 4, 5), integers);
Catatan: Saya seorang pelaku untuk Koleksi Eclipse
sumber
Ketika kami ingin mendapatkan beberapa elemen dari Daftar ke dalam daftar baru (filter menggunakan predikat) dan menghapusnya dari daftar yang ada , saya tidak dapat menemukan jawaban yang tepat di mana pun.
Berikut adalah bagaimana kita dapat melakukannya menggunakan partisi Java Streaming API.
Map<Boolean, List<ProducerDTO>> classifiedElements = producersProcedureActive .stream() .collect(Collectors.partitioningBy(producer -> producer.getPod().equals(pod))); // get two new lists List<ProducerDTO> matching = classifiedElements.get(true); List<ProducerDTO> nonMatching = classifiedElements.get(false); // OR get non-matching elements to the existing list producersProcedureActive = classifiedElements.get(false);
Dengan cara ini Anda secara efektif menghapus elemen yang difilter dari daftar asli dan menambahkannya ke daftar baru.
Lihat 5.2. Collectors.partitioningBy bagian dari artikel ini .
sumber
Seperti yang disarankan orang lain, ini mungkin kasus penggunaan untuk loop dan iterable. Menurut saya, ini adalah pendekatan yang paling sederhana. Jika Anda ingin mengubah daftar di tempat, ini tidak dapat dianggap sebagai pemrograman fungsional "nyata". Tapi Anda bisa menggunakan
Collectors.partitioningBy()
untuk mendapatkan daftar baru dengan elemen yang memenuhi kondisi Anda, dan daftar baru yang tidak memenuhi. Tentu saja dengan pendekatan ini, jika Anda memiliki banyak elemen yang memenuhi syarat, semua itu akan ada di daftar itu dan bukan hanya yang pertama.sumber
Logika di bawah ini adalah solusi tanpa mengubah daftar aslinya
List<String> str1 = new ArrayList<String>(); str1.add("A"); str1.add("B"); str1.add("C"); str1.add("D"); List<String> str2 = new ArrayList<String>(); str2.add("D"); str2.add("E"); List<String> str3 = str1.stream() .filter(item -> !str2.contains(item)) .collect(Collectors.toList()); str1 // ["A", "B", "C", "D"] str2 // ["D", "E"] str3 // ["A", "B", "C"]
sumber
Menggabungkan ide awal saya dan jawaban Anda, saya mencapai apa yang tampaknya menjadi solusi untuk pertanyaan saya sendiri:
public ProducerDTO findAndRemove(String pod) { ProducerDTO p = null; try { p = IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int)i)) .get(); logger.debug(p); } catch (NoSuchElementException e) { logger.error("No producer found with POD [" + pod + "]"); } return p; }
Ini memungkinkan menghapus objek menggunakan
remove(int)
yang tidak melintasi lagi daftar (seperti yang disarankan oleh @Tunaki) dan memungkinkan mengembalikan objek yang dihapus ke pemanggil fungsi.Saya membaca jawaban Anda yang menyarankan saya untuk memilih metode aman seperti
ifPresent
daripadaget
tetapi saya tidak menemukan cara untuk menggunakannya dalam skenario ini.Apakah ada kelemahan penting dalam solusi semacam ini?
Edit mengikuti saran @Holger
Ini harus menjadi fungsi yang saya butuhkan
public ProducerDTO findAndRemove(String pod) { return IntStream.range(0, producersProcedureActive.size()) .filter(i -> producersProcedureActive.get(i).getPod().equals(pod)) .boxed() .findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; }); }
sumber
get
dan menangkap pengecualian. Itu tidak hanya gaya yang buruk tetapi juga dapat menyebabkan kinerja yang buruk. Solusi bersih bahkan lebih sederhana,return /* stream operation*/.findFirst() .map(i -> producersProcedureActive.remove((int)i)) .orElseGet(() -> { logger.error("No producer found with POD [" + pod + "]"); return null; });
tugasnya adalah: mendapatkan ✶ dan ✶ menghapus elemen dari daftar
p.stream().collect( Collectors.collectingAndThen( Collector.of( ArrayDeque::new, (a, producer) -> { if( producer.getPod().equals( pod ) ) a.addLast( producer ); }, (a1, a2) -> { return( a1 ); }, rslt -> rslt.pollFirst() ), (e) -> { if( e != null ) p.remove( e ); // remove return( e ); // get } ) );
sumber