Каков наилучший способ фильтрации коллекции Java?

Я хочу отфильтровать java.util.Collection на основе предиката.

Ответ 1

Java 8 (2014) решает эту проблему, используя потоки и 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

Ответ 2

Предполагая, что вы используете Java 1.5, и что вы не можете добавить Коллекции Google, я бы сделал что-то очень похожее на то, что сделали ребята 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));

UPDATE (после выпуска 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();

API JDK 8 для опций имеет возможность 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.

Ответ 4

Рассмотрим Коллекции Google для обновленной структуры коллекций, которая поддерживает общие файлы.

ОБНОВЛЕНИЕ. Библиотека коллекций google теперь устарела. Вместо этого вы должны использовать последнюю версию Guava. Он все еще имеет все те же расширения к структуре коллекций, включая механизм фильтрации на основе предиката.

Ответ 5

"Лучший" способ - слишком широкий запрос. Это "кратчайший"? "Самый быстрый"? "Удобочитаемый"? Фильтровать на месте или в другую коллекцию?

Простейшим (но не самым читаемым) способом является его повторение и использование метода 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(), чтобы узнать, должен ли экземпляр храниться в коллекции.

Я действительно не вижу оправдания для привлечения сторонней библиотеки только для этой задачи.

Ответ 6

Подождите, пока 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());

Ответ 7

С раннего выпуска 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);

Ответ 8

Я брошу 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

Более подробную информацию о filter RxJava можно найти здесь.

Ответ 10

Настройка:

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

Ответ 11

Как насчет некоторой простой и скрытой 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() статически импортируется

Ответ 12

Давайте посмотрим, как фильтровать встроенный список JDK и MutableList с помощью Коллекции Eclipse (ранее Коллекции GS).

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

Вот несколько альтернатив фильтрации списков JDK и Коллекции Eclipse MutableLists с использованием Predicates factory.

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

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

Вот версия, которая не выделяет объект для предиката, используя Predicates2 factory вместо этого с помощью selectWith, который принимает Predicate2.

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, как 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 будет возвращать две коллекции, содержащие элементы, выбранные и отклоненные 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.

Ответ 14

С 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

Ответ 15

Это, в сочетании с отсутствием реальных закрытий, является моей самой большой проблемой для 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");

Ответ 16

Поскольку 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()));

Ответ 17

Я написал расширенный класс 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);
    }
}

Ответ 18

JFilter http://code.google.com/p/jfilter/ лучше всего подходит для вашего требования.

JFilter - это простая и высокопроизводительная библиотека с открытым исходным кодом для запроса коллекции Java beans.

Основные функции

  • Поддержка свойств коллекции (java.util.Collection, java.util.Map и Array).
  • Поддержка коллекции внутри коллекции любой глубины.
  • Поддержка внутренних запросов.
  • Поддержка параметризованных запросов.
  • Может фильтровать 1 миллион записей за несколько минут.
  • Фильтр (запрос) задается в простом формате json, это похоже на запросы Mangodb. Ниже приведены некоторые примеры.
  • { "id" : { "$ le": "10" }
    • где свойство id объекта меньше, чем 10.
  • { "id" : { "$ in": [ "0", "100" ]}}
    • где свойство id объекта равно 0 или 100.
  • { "lineItems": { "lineAmount": "1" }}
    • где свойство collectionItems параметризованного типа имеет значение lineAmount равно 1.
  • { "$ and": [{ "id" : "0" }, { "billingAddress": { "city": "DEL" }}]}
    • где id - значение 0, а свойство billingAddress.city - DEL.
  • { "lineItems": { "tax": { "key": { "code": "GST" }, "value": { "$ gt": "1.01" }}}}
    • где свойство collectionItems параметризованного типа, у которого есть свойство типа карты налогов с параметризованным типом, имеет код, равный значению GST больше 1.01.
  • {'$ or': [{'code': '10'}, {'skus': {'$ and': [{'price': {'$ in': ['20', '40 ']}}, {' code ':' RedApple '}]}}]}
    • Выбрать все продукты, в которых код продукта равен 10 или sku в 20 и 40, а код sku - "RedApple".

Ответ 20

Некоторые действительно большие отличные ответы здесь. Я, я бы хотел сделать так, чтобы они были максимально просты и понятны:

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

Ответ 21

Простое решение до Java8:

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

К сожалению, это решение не является полностью общим, выводя список, а не тип данной коллекции. Кроме того, приведение в библиотеку или функции записи, которые обертывают этот код, кажется мне излишним, если условие не является сложным, но тогда вы можете написать функцию для условия.

Ответ 22

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

Ответ 23

Мой ответ основывается на том, что от Кевина Вонга, здесь, как однострочный, используя CollectionUtils из spring и выражения Java лямбда Java.

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

Это краткое и читаемое, как любая альтернатива, которую я видел (без использования аспектно-ориентированных библиотек)

Spring CollectionUtils доступен из spring версии 4.0.2.RELEASE, и помните, что вам нужен JDK 1.8 и уровень языка 8 +.

Ответ 24

Мне нужно было отфильтровать список в зависимости от значений, уже присутствующих в списке. Например, удалите все значения, следующие за меньшим, чем текущее значение. {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;
    }
});

Ответ 25

Используя java 8, в частности lambda expression, вы можете сделать это просто как приведенный ниже пример:

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

где для каждого product внутри myProducts коллекции, если prod.price>10, добавьте этот продукт в новый отфильтрованный список.

Ответ 26

С Guava:

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

Ответ 27

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