过滤Java集合的最佳方法是什么?

时间:2020-03-06 14:36:32  来源:igfitidea点击:

我想基于一个谓词过滤一个java.util.Collection

解决方案

使用来自Apache Commons的CollectionUtils.filter(Collection,Predicate)。

考虑将Google Collections用于支持泛型的更新的Collections框架。

更新:谷歌收藏库现已弃用。我们应该使用最新版本的Guava。它仍然具有对集合框架的所有相同扩展,包括基于谓词进行过滤的机制。

我们确定要过滤Collection本身,而不是迭代器吗?

参见org.apache.commons.collections.iterators.FilterIterator

或者使用apache commons的第4版org.apache.commons.collections4.iterators.FilterIterator

设置:

public interface Predicate<T> {
  public boolean filter(T t);
}

void filterCollection(Collection<T> col, Predicate<T> predicate) {
  for (Iterator i = col.iterator(); i.hasNext();) {
    T obj = i.next();
    if (predicate.filter(obj)) {
      i.remove();
    }
  }
}

用法:

List<MyObject> myList = ...;
filterCollection(myList, new Predicate<MyObject>() {
  public boolean filter(MyObject obj) {
    return obj.shouldFilter();
  }
});

"最佳"方式要求太高。它是"最短的"吗? "最快的"? "可读"?
过滤到位还是进入另一个收藏集?

最简单(但不是最易读)的方法是对其进行迭代并使用Iterator.remove()方法:

Iterator<Foo> it = col.iterator();
while( it.hasNext() ) {
  Foo foo = it.next();
  if( !condition(foo) ) it.remove();
}

现在,为了使其更具可读性,我们可以将其包装为实用程序方法。然后发明一个IPredicate接口,创建该接口的匿名实现并执行以下操作:

CollectionUtils.filterInPlace(col,
  new IPredicate<Foo>(){
    public boolean keepIt(Foo foo) {
      return foo.isBar();
    }
  });

其中filterInPlace()迭代集合并调用Predicate.keepIt()以了解实例是否要保留在集合中。

我真的没有理由为此目的引入第三方库。

假设我们正在使用Java 1.5,并且无法添加Google Collections,那么我将执行与Google员工非常相似的操作。这与乔恩的评论略有不同。

首先将此接口添加到代码库中。

public interface IPredicate<T> { boolean apply(T type); }

当某个谓词为某种类型的真时,其实现者可以回答。例如。如果" T"是"用户",并且" AuthorizedUserPredicate <用户>"实现了" IPredicate <T>",则" AuthorizedUserPredicate#apply"将返回传入的"用户"是否被授权。

然后在某些实用程序类中,我们可以说

public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
    Collection<T> result = new ArrayList<T>();
    for (T element: target) {
        if (predicate.apply(element)) {
            result.add(element);
        }
    }
    return result;
}

因此,假设我们已使用上述方法,则可能是

Predicate<User> isAuthorized = new Predicate<User>() {
    public boolean apply(User user) {
        // binds a boolean method in User to a reference
        return user.isAuthorized();
    }
};
// allUsers is a Collection<User>
Collection<User> authorizedUsers = filter(allUsers, isAuthorized);

如果需要关注线性检查的性能,那么我可能想要一个具有目标集合的域对象。具有目标集合的域对象将具有用于初始化,添加和设置目标集合的方法的过滤逻辑。

更新:

在实用程序类中(假设谓词),我添加了一个select方法,当谓词未返回期望值时,该选项带有默认值选项,并且还为要在新IPredicate中使用的params设置了静态属性。

public class Predicate {
    public static Object predicateParams;

    public static <T> Collection<T> filter(Collection<T> target, IPredicate<T> predicate) {
        Collection<T> result = new ArrayList<T>();
        for (T element : target) {
            if (predicate.apply(element)) {
                result.add(element);
            }
        }
        return result;
    }

    public static <T> T select(Collection<T> target, IPredicate<T> predicate) {
        T result = null;
        for (T element : target) {
            if (!predicate.apply(element))
                continue;
            result = element;
            break;
        }
        return result;
    }

    public static <T> T select(Collection<T> target, IPredicate<T> predicate, T defaultValue) {
        T result = defaultValue;
        for (T element : target) {
            if (!predicate.apply(element))
                continue;
            result = element;
            break;
        }
        return result;
    }
}

以下示例在集合之间查找丢失的对象:

List<MyTypeA> missingObjects = (List<MyTypeA>) Predicate.filter(myCollectionOfA,
    new IPredicate<MyTypeA>() {
        public boolean apply(MyTypeA objectOfA) {
            Predicate.predicateParams = objectOfA.getName();
            return Predicate.select(myCollectionB, new IPredicate<MyTypeB>() {
                public boolean apply(MyTypeB objectOfB) {
                    return objectOfB.getName().equals(Predicate.predicateParams.toString());
                }
            }) == null;
        }
    });

下面的示例在一个集合中查找一个实例,并在找不到该实例时将集合的第一个元素作为默认值返回:

MyType myObject = Predicate.select(collectionOfMyType, new IPredicate<MyType>() {
public boolean apply(MyType objectOfMyType) {
    return objectOfMyType.isDefault();
}}, collectionOfMyType.get(0));

UPDATE(在Java 8版本之后):

自从我(Alan)首次发布此答案以来已经有好几年了,但我仍然不敢相信我正在为此答案收集SO点。无论如何,既然Java 8引入了该语言的闭包,我的答案现在将大不相同,并且更加简单。使用Java 8,不需要独特的静态实用程序类。因此,如果要查找与谓词匹配的第一个元素。

final UserService userService = ... // perhaps injected IoC
final Optional<UserModel> userOption = userCollection.stream().filter(u -> {
    boolean isAuthorized = userService.isAuthorized(u);
    return isAuthorized;
}).findFirst();

可选的JDK 8 API具有以下能力:get(),isPresent(),orElse(defaultUser),orElseGet(userSupplier)和orElseThrow(exceptionSupplier),以及其他" monadic"函数,例如map,flatMap和filter。

如果我们只想收集所有与谓词匹配的用户,则可以使用" Collectors"将流终止在所需的集合中。

final UserService userService = ... // perhaps injected IoC
final List<UserModel> userOption = userCollection.stream().filter(u -> {
    boolean isAuthorized = userService.isAuthorized(u);
    return isAuthorized;
}).collect(Collectors.toList());

有关Java 8流如何工作的更多示例,请参见此处。

Google的Guava库中的Collections2.filter(Collection,Predicate)方法可以满足需求。

使用ForEach DSL,我们可以编写

import static ch.akuhn.util.query.Query.select;
import static ch.akuhn.util.query.Query.$result;
import ch.akuhn.util.query.Select;

Collection<String> collection = ...

for (Select<String> each : select(collection)) {
    each.yield = each.value.length() > 3;
}

Collection<String> result = $result();

给定[the,quick,brown,fox,jumps,over,the,lazy,dog]的集合,这将导致[quick,brown,jumps,over,lazy],即所有字符串都超过三个字符。

ForEach DSL支持的所有迭代样式为

  • AllSatisfy
  • 任何满意
  • 收集
  • Counnt
  • 切块
  • 检测
  • GroupedBy
  • IndexOf
  • InjectInto
  • 拒绝
  • 选择

有关更多详细信息,请参阅https://www.iam.unibe.ch/scg/svn_repos/Sources/ForEach

Java 8(2014)在一行代码中使用流和lambda解决了此问题:

List<Person> beerDrinkers = persons.stream()
    .filter(p -> p.getAge() > 16).collect(Collectors.toList());

这是一个教程。

使用Collection#removeIf来修改集合。 (注意:在这种情况下,谓词将删除满足该谓词的对象):

persons.removeIf(p -> p.getAge() <= 16);

lambdaj允许过滤集合而无需编写循环或者内部类:

List<Person> beerDrinkers = select(persons, having(on(Person.class).getAge(),
    greaterThan(16)));

我们能想象一些更具可读性的东西吗?

免责声明:我是lambdaj的撰稿人

再加上缺少真正的闭包,这是我对Java的最大抱怨。
老实说,上面提到的大多数方法都非常易于阅读并且非常有效。但是,在花费时间使用.Net,Erlang等之后,在语言级别集成了列表理解功能,因此一切都变得更加整洁。如果在语言级别没有添加功能,Java就无法像该领域的许多其他语言一样干净。

如果性能是一个非常重要的问题,那么Google Collections是最好的选择(或者编写我们自己的简单谓词实用程序)。 Lambdaj语法对于某些人来说更具可读性,但是效率却不如以前。

然后有一个我写的图书馆。我将忽略有关其效率的任何问题(是的,它的劣质)……是的,我知道它的基于反射的明确含义,并且不,我实际上并没有使用它,但是它确实有效:

LinkedList<Person> list = ......
LinkedList<Person> filtered = 
           Query.from(list).where(Condition.ensure("age", Op.GTE, 21));

或者

LinkedList<Person> list = ....
LinkedList<Person> filtered = Query.from(list).where("x => x.age >= 21");

我编写了扩展的Iterable类,该类支持应用功能算法而不复制集合内容。

用法:

List<Integer> myList = new ArrayList<Integer>(){ 1, 2, 3, 4, 5 }

Iterable<Integer> filtered = Iterable.wrap(myList).select(new Predicate1<Integer>()
{
    public Boolean call(Integer n) throws FunctionalException
    {
        return n % 2 == 0;
    }
})

for( int n : filtered )
{
    System.out.println(n);
}

上面的代码将实际执行

for( int n : myList )
{
    if( n % 2 == 0 ) 
    {
        System.out.println(n);
    }
}