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

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

26 ответов


Java 8 () решает эту проблему, используя потоки и лямбды в одной строке кода:

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

Can вы представляете себе что-то более читаемое?

отказ от ответственности: я являюсь участником lambdaj


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

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

обновление:

в классе утилиты (скажем, предикате) я добавил метод 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();

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


использовать CollectionUtils.фильтр (коллекция,предикат), из Commons "Апач".


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

самый простой (но не самый читаемый) способ-повторить его и использовать итератор.удалить() метод:

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

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


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

обновление: библиотека Google collections теперь устарела. Вы должны использовать последнюю версию гуавы вместо. Он по-прежнему имеет все те же расширения в рамках коллекций, включая механизм фильтрации на основе предиката.


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

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.апаш.палата общин.коллекции.итераторы.FilterIterator

или с помощью версии 4 Apache commons org.апаш.палата общин.collections4.итераторы.FilterIterator


давайте посмотрим, как фильтровать встроенный список 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 с помощью предикаты фабрики.

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

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


С 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], т. е. все строки длиннее трех символов.

все стили итераций, поддерживаемые DSL ForEach are

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

для больше деталей, пожалуйста см.https://www.iam.unibe.ch/scg/svn_repos/Sources/ForEach


на Collections2.фильтр (коллекция,предикат) метод библиотека гуава Google делает только то, что вы ищете.


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

основные возможности

  • поддержка коллекции (java.утиль.Коллекции Ява.утиль.Map and Array) свойства.
  • поддержка коллекции внутри коллекции любой глубины.
  • поддержка внутренних запросов.
  • поддержка параметризованный запрос.
  • может фильтровать 1 миллион записей за несколько 100 мс.
  • фильтр (запрос) задается в простом формате json, это похоже на запросы Mangodb. Ниже приводятся некоторые примеры.
  • {"id": {"$le": "10"}
    • где свойство ID объекта меньше, чем равно 10.
  • {"id": {"$in": ["0", " 100"]}}
    • где свойство ID объекта равно 0 или 100.
  • {"lineItems": {"lineAmount": "1"}}
    • где lineItems собственность коллекция параметризованный тип линии равен 1.
  • {"$and": [{"id": "0"}, {"billingAddress": {"city": "DEL"}}]}
    • где свойство id равно 0 и billingAddress.отель Дель.
  • {"lineItems": {"налоги": {"ключ": {"код": "GST"}, "значение": {"$gt": "1.01"}}}}
    • где lineItems свойства коллекции параметризованного типа, которая не имеет налогов сопоставление свойств типа parameteriszed имеет код равен ГСТ значение больше, чем 1.01.
  • {'$or': [{'code': '10'}, {'skus': {'$and': [{'price': {'$in':['20', '40']}}, {'код': 'RedApple'}]}}]}
    • выберите все продукты, где код продукта 10 или sku цена в 20 и 40 и sku код "RedApple".

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

использовать Collection Query Engine (CQEngine). Это однозначно самый быстрый способ сделать это.

Читайте также: как вы запрашиваете коллекции объектов в Java (критерии/SQL-подобные)?


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

простое решение pre-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);

это так же лаконично и читабельно, как и любая альтернатива, которую я видел (без использования библиотек на основе аспектов)

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


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

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

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

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

где для каждого product внутри myProducts сбор, если 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;
    }
});