如何过滤Java集合(基于谓词)?

我想根据谓词过滤java.util.Collection

803711 次浏览

考虑使用谷歌收藏作为支持泛型的更新集合框架。

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

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

查看org.apache.commons.collections.iterators.FilterIterator

或使用apache共享资源版本4org.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()以了解实例是否要保留在集合中。

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

假设您使用的是Java1.5,并且您不能添加谷歌收藏,我会做一些与Google人员所做的非常相似的事情。这是Jon评论的轻微变化。

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

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

它的实现者可以回答某个谓词何时为特定类型的true。例如。如果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)中,我添加了一个选择方法,当谓词不返回预期值时,该方法带有默认值选项,并且还添加了一个静态属性,用于在新的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));

更新(Java8发布后):

自从我(Alan)第一次发布这个答案已经好几年了,我仍然不敢相信我为这个答案收集了SO分。无论如何,既然Java8已经为这门语言引入了闭包,我的答案现在会有很大的不同,也更简单。有了Java8,不需要一个独特的静态实用程序类。所以如果你想找到第一个与谓词匹配的元素。

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),以及其他“一元”函数,例如mapflatMapfilter

如果您想简单地收集与谓词匹配的所有用户,请使用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());

有关8个流如何工作的更多示例,请参阅这里Java。

使用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,快速,棕色,狐狸,跳跃,在,懒惰,狗]这导致[快速,棕色,跳跃,在,懒惰],即所有字符串长于三个字符。

所有的迭代样式支持的For每个DSL是

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

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

Java8(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集合就是要走的路(或者编写您自己的简单谓词实用程序)。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");

我编写了一个扩展的可迭代类,它支持应用函数式算法而无需复制集合内容。

用法:

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

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

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

主要功能

  • 支持集合(java.util.集合,java.util.映射和数组)属性。
  • 支持任何深度的集合内的集合。
  • 支持内部查询。
  • 支持参数化查询。
  • 可以在100毫秒内过滤100万记录。
  • 过滤器(查询)以简单的json格式给出,就像Mangodb查询。以下是一些示例。
  • {"id":{"$le":"10"}
  • $le>{$le{$le>:"10"}//请求参数
    • 其中对象id属性小于等于10。
  • {"id":{"$in":["0","100"]}
    • 其中对象id属性为0或100。
  • {"line Items":{"line Amount":"1"}}
    • 其中参数化类型的lineItems集合属性的lineAmount等于1。
  • {"$and":[{"id":"0"},{"billingName":{"town":"DEL"}}]}//用户名//用户名//用户名
    • 其中id属性为0,billingAddress.city属性为DEL。
  • {"lineItems":{"税":{"key":{"code":"GST"},"value":{"$gt":"1.01"}}}}
    • 其中参数化类型的lineItems集合属性具有税收参数化类型的映射类型属性具有等于GST值大于1.01的代码。
  • {'$or':[{'code':'10'},{'skus':{'$and':[{'value':{'$in':['20','40']}}, {'code':'RedApple'}]}}]}
    • 选择产品代码为10或sku价格为20和40且sku代码为“RedApple”的所有产品。

让我们看看如何使用Eclipse集合过滤内置JDK List和突变列表

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

以下是如何使用Java8 lambda作为Predicate进行过滤。

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


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

以下是如何使用匿名内部类作为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));

以下是使用谓词工厂过滤JDK列表和Eclipse集合 MutableList的一些替代方案。

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

这是一个不为谓词分配对象的版本,通过使用预测2工厂而不是使用selectWith方法,该方法采用Predicate2

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

有时您想在否定条件下进行过滤。Eclipse集合中有一个名为reject的特殊方法。

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

方法partition将返回两个集合,其中包含由Predicate选择和拒绝的元素。

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集合的提交者。

等待Java8:

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

从Java8的早期版本开始,您可以尝试以下操作:

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

来点简单直接的Java

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

简单,易读和容易(并在Android工作!) 但是如果你使用Java8,你可以在一个甜蜜的一行中做到这一点:

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

注意toList()是静态导入的

简单的pre-Java8解决方案:

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

不幸的是,这个解决方案不是完全通用的,输出一个列表而不是给定集合的类型。此外,引入库或编写包装此代码的函数对我来说似乎有点矫枉过正,除非条件很复杂,但你可以为条件编写一个函数。

我将把RxJava扔进环中,它也在android上可用。RxJava可能并不总是最佳选择,但如果您希望在集合中添加更多转换或在过滤时处理错误,它将为您提供更多灵活性。

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的更多详细信息,请参阅这里

支持不同的可能性,

鉴于收藏,

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

的类型,

class Dto
{
private int id;
private String text;


public int getId()
{
return id;
}


public int getText()
{
return text;
}
}

过滤器

Java7

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

Java8

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

排序(也可用于Java7)

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

分组(也可用于Java7)

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

加入(也可用于Java7)

鉴于,

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

可以加入像,

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

表达式

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

我的回答建立在Kevin Wong的基础上,这里使用弹簧中的CollectionUtils和Java8lambda表达式。

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

这和我见过的任何替代方案一样简洁易读(不使用基于方面的库)

Spring曲目Utils可从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;
}
});

由于启用了java 9Collectors.filtering

public static <T, A, R>
Collector<T, ?, R> filtering(Predicate<? super T> predicate,
Collector<? super T, A, R> downstream)

因此,过滤应该是:

collection.stream().collect(Collectors.filtering(predicate, collector))

示例:

List<Integer> oddNumbers = List.of(1, 19, 15, 10, -10).stream()
.collect(Collectors.filtering(i -> i % 2 == 1, Collectors.toList()));

在Java8中,您可以直接使用此过滤方法,然后执行此操作。

 List<String> lines = Arrays.asList("java", "pramod", "example");


List<String> result = lines.stream()
.filter(line -> !"pramod".equals(line))
.collect(Collectors.toList());


result.forEach(System.out::println);

在我的例子中,我正在寻找排除特定字段null的列表。 这可以通过for循环完成,并填充没有空地址的对象的临时列表。 多亏了Java8流

List<Person> personsList = persons.stream()
.filter(p -> p.getAdrress() != null).collect(Collectors.toList());

#java#集合#集合#java8#流

Java集合流的替代(更轻量级)是Ocl.java库,它使用vanilla集合和lambdas:https://github.com/eclipse/agileuml/blob/master/Ocl.java

例如,对ArrayList单词进行简单的过滤和求和 可以是:

ArrayList<Word> sel = Ocl.selectSequence(words,
w -> w.pos.equals("NN"));
int total = Ocl.sumint(Ocl.collectSequence(sel,
w -> w.text.length()));

其中Word具有String pos;String text;属性。效率似乎与流选项相似,例如,两个版本都在大约50毫秒内处理10000个单词。

Python、Swift等有等效的OCL库。基本上Java收集流重新发明了OCL操作->选择、->收集等,这些操作自1998年以来就存在于OCL中。