为什么没有 Lists.filter()函数?

是不是有什么原因

Lists.transform()

但是没有

Lists.filter()

如何正确过滤列表? 我可以使用

new ArrayList(Collection2.filter())

当然,但这样就不能保证我的订单不变,如果我理解正确的话。

43874 次浏览

You can use Iterables.filter, which will definitely maintain ordering.

Note that by constructing a new list, you'll be copying the elements (just references, of course) - so it won't be a live view onto the original list. Creating a view would be pretty tricky - consider this situation:

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);

That would have to iterate over the whole original list, applying the filter to everything. I suppose it could require that the predicate matching didn't change over the lifetime of the view, but that would be not-entirely-satisfactory.

(This is just guessing, mind you. Maybe one of the Guava maintainers will chip in with the real reason :)

As mentioned by Jon, you can use Iterables.filter(..) or Collections2.filter(..) and if you don't need a live view you can use ImmutableList.copyOf(Iterables.filter(..)) or Lists.newArrayList( Iterables.filter(..)) and yes ordering will be maintained.

If you are really interested in why part, you can visit https://github.com/google/guava/issues/505 for more details.

I could use new List(Collection2.filter()) of course, but this way it's not guaranteed that my ordering stays the same.

This isn't true. Collections2.filter() is a lazily-evaluated function - it doesn't actually filter your collection until you start accessing the filtered version. For example, if you iterate over the filtered version, then the filtered elements will pop out of the iterator in the same order as your original collection (minus the ones filtered out, obviously).

Perhaps you were thinking that it does the filtering up front, then dumps the results into an arbitrary, unordered Collection of some form - it doesn't.

So if you use the output of Collections2.filter() as the input to a new list, then your original order will be retained.

Using static imports (and the Lists.newArrayList function), it becomes fairly succinct:

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

Note that while Collections2.filter will not eagerly iterate over the underlying collection, Lists.newArrayList will - it will extract all elements of the filtered collection and copy them into a new ArrayList.

It wasn't implemented because it would expose a perilous large number of slow methods, such as #get(index) on the returned List view (inviting performance bugs). And ListIterator would be a pain to implement as well (though I submitted a patch years ago to cover that).

Since indexed methods can't be efficient in the filtered List view, it's better to just go with a filtered Iterable, which doesn't have them.

Summing up what the others said, you can easily create a generic wrapper to filter lists:

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