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

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

Java 8( 2014 )在一行代码中使用stream和lambdas解决了这个问题:

 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 1.5 ,并且你不能添加Google Collections ,那么我会做一些与Google的工作非常相似的工作。 Jon的评论略有不同。

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

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

它的实现者可以回答某个谓词是否属于某种types的情况。 例如,如果TUserAuthorizedUserPredicate<User>实现了IPredicate<T> ,则AuthorizedUserPredicate#apply返回是否允许传入的User

那么在一些实用课上,你可以说

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

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

更新:

在工具类(比如Predicate)中,我添加了一个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; } } 

以下示例查找集合之间的缺less对象:

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

更新(在Java 8发布后):

我(艾伦)第一次发布这个答案已经有好几年了,我仍然不敢相信我正在收集这个答案的SO点。 无论如何,现在Java 8引入了closures语言,现在我的答案会有很大的不同,而且更简单。 使用Java 8,不需要一个独特的静态工具类。 所以如果你想find与你的谓词相匹配的第一个元素。

 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”函数,如mapflatMapfilter

如果您只想收集与谓词匹配的所有用户,则使用Collectors来终止所需集合中的stream。

 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 8stream如何工作的更多示例,请参阅此处 。

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

考虑Google Collections获取支持generics的更新的Collections框架。

更新 :谷歌集合库现在已被弃用。 您应该使用最新版本的番石榴 。 它仍然具有与集合框架相同的扩展,包括基于谓词进行过滤的机制。

“最好”的方式太广泛了。 这是“最短的”吗? “最快的”? “读”? 过滤到位或进入另一个集合?

最简单(但不是最可读)的方法是迭代它并使用Iterator.remove()方法:

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

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

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

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

我真的没有看到为这个任务引入第三方库的理由。

等待Java 8:

 List<Person> olderThan30 = //Create a Stream from the personList personList.stream(). //filter the element to select only those with age >= 30 filter(p -> p.age >= 30). //put those filtered elements into a new List. collect(Collectors.toList()); 

从Java 8的早期版本开始,您可以尝试如下所示:

 Collection<T> collection = ...; Stream<T> stream = collection.stream().filter(...); 

例如,如果您有一个整数列表,并且您想要筛选大于10的数字,然后将这些数字打印到控制台,则可以执行如下操作:

 List<Integer> numbers = Arrays.asList(12, 74, 5, 8, 16); numbers.stream().filter(n -> n > 10).forEach(System.out::println); 

我会把RxJava扔在戒指上,也可以在Android上使用 。 RxJava可能并不总是最好的select,但是如果你希望在你的集合上添加更多的转换或者在过滤的时候处理错误,它会给你更多的灵活性。

 Observable.from(Arrays.asList(1, 2, 3, 4, 5)) .filter(new Func1<Integer, Boolean>() { public Boolean call(Integer i) { return i % 2 != 0; } }) .subscribe(new Action1<Integer>() { public void call(Integer i) { System.out.println(i); } }); 

输出:

 1 3 5 

关于RxJava的filter更多细节可以在这里find。

设置:

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

你确定要过滤集合本身,而不是迭代器吗?

请参阅org.apache.commons.collections.iterators.FilterIterator

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

我们来看看如何使用Eclipse集合 (以前的GS集合 )过滤内置的JDK列表和MutableList 。

 List<Integer> jdkList = Arrays.asList(1, 2, 3, 4, 5); MutableList<Integer> ecList = Lists.mutable.with(1, 2, 3, 4, 5); 

如果你想过滤小于3的数字,你会期望以下输出。

 List<Integer> selected = Lists.mutable.with(1, 2); List<Integer> rejected = Lists.mutable.with(3, 4, 5); 

以下是如何使用匿名内部类作为Predicate进行过滤。

 Predicate<Integer> lessThan3 = new Predicate<Integer>() { public boolean accept(Integer each) { return each < 3; } }; Assert.assertEquals(selected, Iterate.select(jdkList, lessThan3)); Assert.assertEquals(selected, ecList.select(lessThan3)); 

以下是使用Predicates工厂过滤JDK列表和Eclipse Collections MutableLists的一些替代方法。

 Assert.assertEquals(selected, Iterate.select(jdkList, Predicates.lessThan(3))); Assert.assertEquals(selected, ecList.select(Predicates.lessThan(3))); 

这是一个不为谓词分配对象的版本,通过使用Predicates2工厂而不是使用带Predicate2的selectWith方法。

 Assert.assertEquals( selected, ecList.selectWith(Predicates2.<Integer>lessThan(), 3)); 

有时候你想过滤一个消极的条件。 Eclipse Collections中有一个特殊的方法叫做reject

 Assert.assertEquals(rejected, Iterate.reject(jdkList, lessThan3)); Assert.assertEquals(rejected, ecList.reject(lessThan3)); 

以下是如何使用Java 8 lambda作为Predicate进行筛选的方法。

 Assert.assertEquals(selected, Iterate.select(jdkList, each -> each < 3)); Assert.assertEquals(rejected, Iterate.reject(jdkList, each -> each < 3)); Assert.assertEquals(selected, gscList.select(each -> each < 3)); Assert.assertEquals(rejected, gscList.reject(each -> each < 3)); 

方法partition将返回两个集合,包含Predicateselect和拒绝的元素。

 PartitionIterable<Integer> jdkPartitioned = Iterate.partition(jdkList, lessThan3); Assert.assertEquals(selected, jdkPartitioned.getSelected()); Assert.assertEquals(rejected, jdkPartitioned.getRejected()); PartitionList<Integer> ecPartitioned = gscList.partition(lessThan3); Assert.assertEquals(selected, ecPartitioned.getSelected()); Assert.assertEquals(rejected, ecPartitioned.getRejected()); 

注意:我是Eclipse集合的提交者。

使用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(); 

鉴于[快速,棕色,狐狸,跳跃,超过,懒惰,狗]的集合,这将导致[快速,棕色,跳跃,结束,懒惰],即所有string超过三个字符。

ForEach DSL支持的所有迭代样式都是

  • AllSatisfy
  • AnySatisfy
  • Collect
  • Counnt
  • CutPieces
  • Detect
  • GroupedBy
  • IndexOf
  • InjectInto
  • Reject
  • Select

欲了解更多详情,请参阅https://www.iam.unibe.ch/scg/svn_repos/Sources/ForEach

谷歌Guava图书馆的Collections2.filter(Collection,Predicate)方法正好满足你的要求。

怎么样一些简单而直接的Java

  List<Customer> list ...; List<Customer> newList = new ArrayList<>(); for (Customer c : list){ if (c.getName().equals("dd")) newList.add(c); } 

简单,易读和简单(并且可以在Android中运行)但是如果您使用的是Java 8,则可以使用以下方法:

 List<Customer> newList = list.stream().filter(c -> c.getName().equals("dd")).collect(toList()); 

请注意,toList()是静态导入的

这加上缺乏真正的closures,是我对Java最大的抱怨。 说实话,上面提到的大多数方法都很容易阅读,而且效率很高。 然而,花费时间与.Net,Erlang等…在语言层面上集成的列表理解使得一切都变得更加清洁。 没有语言层面的补充,Java就不能像这个领域的许多其他语言一样干净。

如果性能是一个巨大的问题,Google集合是要走的路(或者编写自己的简单的谓词实用程序)。 对于某些人来说,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"); 

JFilter http://code.google.com/p/jfilter/最适合您的要求。;

JFilter是一个简单而高性能的开源库,用于查询Java bean的集合。

主要特征

  • 支持collection(java.util.Collection,java.util.Map和Array)属性。
  • 支持任何深度收集里面的集合。
  • 支持内部查询。
  • 支持参数化查询。
  • 可以在几百毫秒内过滤100万条logging。
  • filter(查询)以简单的json格式给出,就像Mangodb查询。 以下是一些例子。
  • {“id”:{“$ le”:“10”}
    • 对象id属性小于等于10。
  • {“id”:{“$ in”:[“0”,“100”]}}
    • 对象id属性是0或100。
  • { “了LineItem”:{ “lineAmount”: “1”}}
    • 其中参数化types的lineItems集合属性的lineAmount等于1。
  • {“$ and”:[{“id”:“0”},{“billingAddress”:{“city”:“DEL”}}]}
    • 其中id属性为0,而billingAddress.city属性为DEL。
  • {“lineItems”:{“taxes”:{“key”:{“code”:“GST”},“value”:{“$ gt”:“1.01”}}}}
    • 其中具有参数化types的参数化types的lineItems集合属性的代码等于大于1.01的GST值。
  • {'$ or':[{'code':'10'},{'skus':{'$ and':[{'price':{'$ in':['20','40']} },{'code':'RedApple'}]}}]}
    • select产品代码为10或sku价格为20,40的所有产品,sku代码为“RedApple”。

我写了一个扩展的Iterable类 ,它支持应用函数algorithm而不复制集合内容。

用法:

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

使用集合查询引擎(CQEngine) 。 这是迄今为止最快的方式来做到这一点。

另请参阅: 如何查询Java中的对象集合(Criteria / SQL-like)?

简单的Java8之前的解决scheme:

 ArrayList<Item> filtered = new ArrayList<Item>(); for (Item item : items) if (condition(item)) filtered.add(item); 

不幸的是,这个解决scheme不是完全通用的,输出一个列表而不是给定集合的types。 此外,引入库或编写函数来包装这些代码似乎对我来说是过度的,除非条件复杂,但是可以为条件编写一个函数。

https://code.google.com/p/joquery/

支持不同的可能性,

鉴于收集,

 Collection<Dto> testList = new ArrayList<>(); 

types,

 class Dto { private int id; private String text; public int getId() { return id; } public int getText() { return text; } } 

过滤

Java 7

 Filter<Dto> query = CQ.<Dto>filter(testList) .where() .property("id").eq().value(1); Collection<Dto> filtered = query.list(); 

Java 8

 Filter<Dto> query = CQ.<Dto>filter(testList) .where() .property(Dto::getId) .eq().value(1); Collection<Dto> filtered = query.list(); 

也,

 Filter<Dto> query = CQ.<Dto>filter() .from(testList) .where() .property(Dto::getId).between().value(1).value(2) .and() .property(Dto::grtText).in().value(new string[]{"a","b"}); 

sorting (也可用于Java 7)

 Filter<Dto> query = CQ.<Dto>filter(testList) .orderBy() .property(Dto::getId) .property(Dto::getName) Collection<Dto> sorted = query.list(); 

分组 (也可用于Java 7)

 GroupQuery<Integer,Dto> query = CQ.<Dto,Dto>query(testList) .group() .groupBy(Dto::getId) Collection<Grouping<Integer,Dto>> grouped = query.list(); 

联接 (也可用于Java 7)

鉴于,

 class LeftDto { private int id; private String text; public int getId() { return id; } public int getText() { return text; } } class RightDto { private int id; private int leftId; private String text; public int getId() { return id; } public int getLeftId() { return leftId; } public int getText() { return text; } } class JoinedDto { private int leftId; private int rightId; private String text; public JoinedDto(int leftId,int rightId,String text) { this.leftId = leftId; this.rightId = rightId; this.text = text; } public int getLeftId() { return leftId; } public int getRightId() { return rightId; } public int getText() { return text; } } Collection<LeftDto> leftList = new ArrayList<>(); Collection<RightDto> rightList = new ArrayList<>(); 

可以join像,

 Collection<JoinedDto> results = CQ.<LeftDto, LeftDto>query().from(leftList) .<RightDto, JoinedDto>innerJoin(CQ.<RightDto, RightDto>query().from(rightList)) .on(LeftFyo::getId, RightDto::getLeftId) .transformDirect(selection -> new JoinedDto(selection.getLeft().getText() , selection.getLeft().getId() , selection.getRight().getId()) ) .list(); 

expression式

 Filter<Dto> query = CQ.<Dto>filter() .from(testList) .where() .exec(s -> s.getId() + 1).eq().value(2); 

这里有一些真正伟大的答案。 我,我想尽可能保持简单和可读性:

 public abstract class AbstractFilter<T> { /** * Method that returns whether an item is to be included or not. * @param item an item from the given collection. * @return true if this item is to be included in the collection, false in case it has to be removed. */ protected abstract boolean excludeItem(T item); public void filter(Collection<T> collection) { if (CollectionUtils.isNotEmpty(collection)) { Iterator<T> iterator = collection.iterator(); while (iterator.hasNext()) { if (excludeItem(iterator.next())) { iterator.remove(); } } } } 

}

我的答案build立在Kevin Wong的基础之上,这里是Spring的 CollectionUtils和Java 8 lambdaexpression式的单线程

 CollectionUtils.filter(list, p -> ((Person) p).getAge() > 16); 

这与我所看到的任何替代方法一样简洁可读(不使用基于方面的库)

Spring CollectionUtils从Spring 4.0.2.RELEASE版本开始提供,请记住您需要JDK 1.8和8+语言版本。

使用java 8 ,特别是lambda expression ,你可以像下面的例子那样做:

 myProducts.stream().filter(prod -> prod.price>10).collect(Collectors.toList()) 

对于myProducts集合中的每个product ,如果prod.price>10 ,则将此产品添加到新的过滤列表。

番石榴:

 Collection<Integer> collection = Lists.newArrayList(1, 2, 3, 4, 5); Iterators.removeIf(collection.iterator(), new Predicate<Integer>() { @Override public boolean apply(Integer i) { return i % 2 == 0; } }); System.out.println(collection); // Prints 1, 3, 5 

我需要根据已经存在于列表中的值来过滤列表。 例如,删除后面的所有值都小于当前值。 {2 5 3 4 7 5} – > {2 5 7}。 或者例如删除所有重复的{3 5 4 2 3 5 6} – > {3 5 4 2 6}。

 public class Filter { public static <T> void List(List<T> list, Chooser<T> chooser) { List<Integer> toBeRemoved = new ArrayList<>(); leftloop: for (int right = 1; right < list.size(); ++right) { for (int left = 0; left < right; ++left) { if (toBeRemoved.contains(left)) { continue; } Keep keep = chooser.choose(list.get(left), list.get(right)); switch (keep) { case LEFT: toBeRemoved.add(right); continue leftloop; case RIGHT: toBeRemoved.add(left); break; case NONE: toBeRemoved.add(left); toBeRemoved.add(right); continue leftloop; } } } Collections.sort(toBeRemoved, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; } }); for (int i : toBeRemoved) { if (i >= 0 && i < list.size()) { list.remove(i); } } } public static <T> void List(List<T> list, Keeper<T> keeper) { Iterator<T> iterator = list.iterator(); while (iterator.hasNext()) { if (!keeper.keep(iterator.next())) { iterator.remove(); } } } public interface Keeper<E> { boolean keep(E obj); } public interface Chooser<E> { Keep choose(E left, E right); } public enum Keep { LEFT, RIGHT, BOTH, NONE; } } 

这将使用像这样。

 List<String> names = new ArrayList<>(); names.add("Anders"); names.add("Stefan"); names.add("Anders"); Filter.List(names, new Filter.Chooser<String>() { @Override public Filter.Keep choose(String left, String right) { return left.equals(right) ? Filter.Keep.LEFT : Filter.Keep.BOTH; } });