Я хочу фильтровать на java.util.Collection
основе предиката.
Я хочу фильтровать на java.util.Collection
основе предиката.
Ответы:
Java 8 ( 2014 ) решает эту проблему, используя потоки и лямбды в одной строке кода:
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
persons.removeIf(p -> p.getAge() <= 16);
Предполагая, что вы используете Java 1.5 , и что вы не можете добавить Google Collections , я бы сделал нечто очень похожее на то, что сделали ребята из Google. Это небольшое изменение в комментариях Джона.
Сначала добавьте этот интерфейс в вашу кодовую базу.
public interface IPredicate<T> { boolean apply(T type); }
Его разработчики могут ответить, когда определенный предикат является истинным для определенного типа. Например, если T
были User
и AuthorizedUserPredicate<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.
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));
ОБНОВЛЕНИЕ (после выпуска Java 8):
Прошло несколько лет с тех пор, как я (Алан) впервые опубликовал этот ответ, и я до сих пор не могу поверить, что набираю ТАК очки за этот ответ. Во всяком случае, теперь, когда Java 8 ввела замыкания в языке, мой ответ теперь будет значительно другим и более простым. В Java 8 нет необходимости в отдельном статическом служебном классе. Так что если вы хотите найти 1-й элемент, который соответствует вашему предикату.
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)
, а также другую «монадическую» функция , такие как 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.
val authorized = for (user <- users if user.isAuthorized) yield user
Используйте CollectionUtils.filter (Collection, Predicate) от Apache Commons.
«Лучший» способ - это слишком широкий запрос. Это "самый короткий"? «Самый быстрый»? "Удобочитаемый"? Фильтр на месте или в другую коллекцию?
Простейший (но не самый читаемый) способ - выполнить итерацию и использовать метод 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 (), чтобы узнать, будет ли экземпляр храниться в коллекции.
Я не вижу оправдания для привлечения сторонней библиотеки только для этой задачи.
stream()
особенность, но не каждый может поиграть с новейшими игрушками: P
Рассмотрим Google Collections для обновленной структуры Collections, которая поддерживает дженерики.
ОБНОВЛЕНИЕ : Библиотека коллекций Google устарела. Вместо этого вы должны использовать последнюю версию Guava . Он все еще имеет все те же расширения для структуры коллекций, включая механизм фильтрации на основе предиката.
Ждите 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());
personList.removeIf(p -> p.age < 30);
Меньше многословно. Кроме того, я слышал разговоры о том, чтобы начать реализовывать apis, которые принимают и возвращают Stream
s, а не Collection
s, потому что Stream
s очень полезны и быстры, но идти к ним и из них идет медленно.
Начиная с раннего выпуска 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 не всегда может быть лучшим вариантом, но он даст вам больше гибкости, если вы захотите добавить больше преобразований в свою коллекцию или обработать ошибки во время фильтрации.
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
можно найти здесь .
Настройка:
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();
}
});
Как насчет простой и понятной 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 () статически импортируется
Вы уверены, что хотите отфильтровать саму коллекцию, а не итератор?
см. org.apache.commons.collections.iterators.FilterIterator
или используя версию 4 apache commons org.apache.commons.collections4.iterators.FilterIterator
Давайте посмотрим, как фильтровать встроенный список JDK и список MutableList с использованием коллекций Eclipse .
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);
Вот как вы можете фильтровать, используя лямбду Java 8 в качестве 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 и MutableList коллекций Eclipse с использованием фабрики Predicates .
Assert.assertEquals(selected, Iterate.select(jdkList, Predicates.lessThan(3)));
Assert.assertEquals(selected, ecList.select(Predicates.lessThan(3)));
Вот версия, которая не выделяет объект для предиката, используя вместо этого фабрику Predicates2 с 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 Collections.
removeIf
списке или установить для примитивов?
С 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();
Для данной коллекции [Быстрый, коричневый, лиса, прыжки, через, ленивый, собака] это приводит к [быстрому, коричневому, прыжки, через, ленивый], то есть все строки длиннее трех символов.
Все стили итераций, поддерживаемые ForEach DSL,
AllSatisfy
AnySatisfy
Collect
Counnt
CutPieces
Detect
GroupedBy
IndexOf
InjectInto
Reject
Select
Для получения более подробной информации, пожалуйста, обратитесь к https://www.iam.unibe.ch/scg/svn_repos/Sources/ForEach
Метод Collections2.filter (Collection, Predicate) в библиотеке Google Guava делает именно то, что вы ищете.
Поскольку java 9 Collectors.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()));
Это, в сочетании с отсутствием реальных замыканий, является моей самой большой проблемой для Java. Честно говоря, большинство методов, упомянутых выше, довольно легко читать и ДЕЙСТВИТЕЛЬНО эффективно; однако, потратив время на .Net, Erlang и т. д., понимание списка, интегрированное на уровне языка, делает все намного чище. Без дополнений на уровне языка Java не может быть настолько чистым, как многие другие языки в этой области.
Если производительность очень важна, то коллекции Google - это то, что нужно (или напишите собственную простую утилиту предикатов). Синтаксис лямбдажа более понятен для некоторых людей, но он не так эффективен.
А потом есть библиотека, которую я написал. Я буду игнорировать любые вопросы, касающиеся его эффективности (да, это так плохо) ...... Да, я знаю, что он основан на отражениях, и нет, на самом деле я его не использую, но он работает:
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-бинов.
Ключевая особенность
Я написал расширенный класс 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);
}
}
Используйте механизм сбора запросов (CQEngine) . На сегодняшний день это самый быстрый способ сделать это.
См. Также: Как вы запрашиваете коллекции объектов в Java (Criteria / SQL-like)?
Некоторые действительно отличные ответы здесь. Я бы хотел, чтобы текст был максимально простым и читабельным:
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();
}
}
}
}
}
Простое решение до Java8:
ArrayList<Item> filtered = new ArrayList<Item>();
for (Item item : items) if (condition(item)) filtered.add(item);
К сожалению, это решение не является полностью общим, выводя список, а не тип данной коллекции. Кроме того, привлечение библиотек или написание функций, которые обертывают этот код, кажется мне излишним, если условие не сложное, но тогда вы можете написать функцию для условия.
https://code.google.com/p/joquery/
Поддерживает разные возможности,
Данная коллекция,
Collection<Dto> testList = new ArrayList<>();
типа,
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"});
Сортировка (также доступна для 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<>();
Может быть присоединен как,
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);
Мой ответ основан на этом от Кевина Вонга, здесь как однострочный, использующий CollectionUtils
от весны и лямбда- выражения Java 8 .
CollectionUtils.filter(list, p -> ((Person) p).getAge() > 16);
Это так же кратко и читабельно, как и любая альтернатива, которую я видел (без использования библиотек на основе аспектов)
Spring CollectionUtils доступен с весенней версии 4.0.2.RELEASE, и помните, что вам нужен JDK 1.8 и языковой уровень 8+.
Используя java 8
, в частности lambda expression
, вы можете сделать это просто, как показано ниже:
myProducts.stream().filter(prod -> prod.price>10).collect(Collectors.toList())
где для каждой product
внутренней myProducts
коллекции, если prod.price>10
, а затем добавить этот продукт в новый отфильтрованный список.
Мне нужно было отфильтровать список в зависимости от значений, уже присутствующих в списке. Например, удалите все значения, следующие ниже, чем текущее значение. {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;
}
});
С гуавой:
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
В Java 8 вы можете напрямую использовать этот метод фильтра, а затем сделать это.
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);