Guava: Mengapa tidak ada fungsi Lists.filter ()?

86

Apakah ada alasannya

Lists.transform()

tapi tidak

Lists.filter()

?

Bagaimana cara memfilter daftar dengan benar? Saya bisa menggunakan

new ArrayList(Collection2.filter())

tentu saja, tapi dengan cara ini tidak ada jaminan bahwa pemesanan saya tetap sama, jika saya mengerti dengan benar.

Fabian Zeindl
sumber
8
FYI, List.newArrayList (Iterables.filter (...)) umumnya lebih cepat daripada ArrayList baru (Collection2.filter (...)). Konstruktor ArrayList memanggil size () pada koleksi yang difilter, dan menghitung ukuran tersebut mengharuskan filter diterapkan ke setiap elemen dari daftar asli.
Jared Levy
4
@JaredLevy Mungkin bukannya List.newArrayList(Iterables.filter(...)), seharusnya dikatakan Lists.newArrayList(Iterables.filter(...)) .
Abdull

Jawaban:

58

Itu tidak diterapkan karena akan mengekspos sejumlah besar metode lambat yang berbahaya, seperti #get (indeks) pada tampilan Daftar yang dikembalikan (mengundang bug kinerja). Dan ListIterator akan menyusahkan untuk diterapkan juga (meskipun saya mengirimkan tambalan bertahun-tahun yang lalu untuk membahasnya).

Karena metode yang diindeks tidak dapat menjadi efisien dalam tampilan Daftar yang difilter, lebih baik menggunakan Iterable yang difilter, yang tidak memilikinya.

Dimitris Andreou
sumber
7
Anda mengasumsikan bahwa tampilan daftar akan dikembalikan. Namun #filter dapat diimplementasikan sebagai mengembalikan daftar terwujud baru, yang sebenarnya adalah apa yang saya harapkan dari metode filter untuk daftar yang bertentangan dengan yang ada di Iterable.
Felix Leipold
@FelixLeipold Namun ini akan membuat air keruh. Karena adanya, filtersecara konsisten berarti pandangan (bersama dengan perilaku yang menyiratkan) apakah itu Iterables.filter, Sets.filterdll. Karena Iterables.filtermudah digabungkan dengan copyOfapa pun ImmutableCollection, saya menemukan ini trade-off desain yang baik (vs datang dengan metode & nama tambahan, seperti filteredCopyatau yang lainnya , untuk kombinasi utilitas sederhana).
Luke Usherwood
37

Kamu dapat memakai Iterables.filter , yang pasti akan menjaga pemesanan.

Perhatikan bahwa dengan membuat daftar baru, Anda akan menyalin elemen (hanya referensi, tentu saja) - jadi ini tidak akan menjadi tampilan langsung ke daftar asli. Membuat tampilan akan sangat sulit - pertimbangkan situasi ini:

Predicate<StringBuilder> predicate = 
    /* predicate returning whether the builder is empty */
List<StringBuilder> builders = Lists.newArrayList();
List<StringBuilder> view = Lists.filter(builders, predicate);

for (int i = 0; i < 10000; i++) {
    builders.add(new StringBuilder());
}
builders.get(8000).append("bar");

StringBuilder firstNonEmpty = view.get(0);

Itu harus mengulang seluruh daftar asli, menerapkan filter ke semuanya. Saya kira itu bisa mengharuskan pencocokan predikat tidak berubah selama masa tampilan, tetapi itu tidak akan sepenuhnya memuaskan.

(Ini hanya menebak-nebak, ingat. Mungkin salah satu pengelola Jambu Biji akan menyumbang dengan alasan sebenarnya :)

Jon Skeet
sumber
1
Collections2.filter.iteratorhanya panggilan Iterables.filter, jadi hasilnya sama.
Skaffman
@skaffman: Dalam hal ini saya akan menggunakan Iterables.filterversi hanya untuk kejelasan.
Jon Skeet
3
... kecuali jika Anda memerlukan suatu view.size()tempat nanti dalam kode :)
Xaerxess
28

Saya dapat menggunakan new List(Collection2.filter())tentu saja, tetapi dengan cara ini tidak ada jaminan bahwa pemesanan saya tetap sama.

Ini tidak benar. Collections2.filter()adalah fungsi yang dievaluasi dengan lambat - fungsi ini tidak benar-benar memfilter koleksi Anda sampai Anda mulai mengakses versi yang difilter. Misalnya, jika Anda mengulang versi yang difilter, elemen yang difilter akan keluar dari iterator dengan urutan yang sama seperti koleksi asli Anda (kecuali yang difilter, tentunya).

Mungkin Anda berpikir bahwa ia melakukan pemfilteran di depan, lalu membuang hasilnya ke dalam bentuk Koleksi yang sewenang-wenang dan tidak berurutan - ternyata tidak.

Jadi jika Anda menggunakan keluaran dari Collections2.filter()sebagai masukan ke daftar baru, maka pesanan asli Anda akan dipertahankan.

Menggunakan impor statis (dan Lists.newArrayListfungsinya), ini menjadi cukup singkat:

List filteredList = newArrayList(filter(originalList, predicate));

Perhatikan bahwa sementara Collections2.filtertidak akan bersemangat mengulang koleksi yang mendasarinya, Lists.newArrayList akan - itu akan mengekstrak semua elemen dari koleksi yang difilter dan menyalinnya ke yang baru ArrayList.

skaffman
sumber
Ini lebih seperti: List filteredList = newArrayList(filter(originalList, new Predicate<T>() { @Override public boolean apply(T input) { return (...); } }));atau ie. List filteredList = newArrayList(filter(originalList, Predicates.notNull()));
Xaerxess
@Xaerxess: Ups, ya, lupa predikatnya ... tetap
skaffman
@ Bozho: Terima kasih ... butuh waktu cukup lama :)
Skaffman
12

Seperti yang disebutkan oleh Jon, Anda dapat menggunakan Iterables.filter(..)atau Collections2.filter(..)dan jika Anda tidak memerlukan tampilan langsung, Anda dapat menggunakan ImmutableList.copyOf(Iterables.filter(..))atau Lists.newArrayList( Iterables.filter(..))dan ya pemesanan akan dipertahankan.

Jika Anda benar-benar tertarik dengan why part, Anda dapat mengunjungi https://github.com/google/guava/issues/505 untuk lebih jelasnya.

Premraj
sumber
6

Meringkas apa yang dikatakan orang lain, Anda dapat dengan mudah membuat pembungkus umum untuk memfilter daftar:

public static <T> List<T> filter(Iterable<T> userLists, Predicate<T> predicate) {
    return Lists.newArrayList(Iterables.filter(userLists, predicate));
}
Holger Brandl
sumber