Каков наилучший способ фильтрации коллекции Java? - PullRequest
627 голосов
/ 23 сентября 2008

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

Ответы [ 27 ]

630 голосов
/ 06 сентября 2009

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

218 голосов
/ 23 сентября 2008

Если вы используете Java 1.5 и не можете добавить Google Collections , я бы сделал нечто очень похожее на то, что сделали ребята из Google. Это небольшое изменение в комментариях Джона.

Сначала добавьте этот интерфейс в свою кодовую базу.

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

Его разработчики могут ответить, когда определенный предикат имеет значение true определенного типа. Например. Если 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.

90 голосов
/ 23 сентября 2008

Использование CollectionUtils.filter (Collection, Predicate) , от Apache Commons.

65 голосов
/ 23 сентября 2008

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

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

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

62 голосов
/ 23 сентября 2008

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

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

25 голосов
/ 29 августа 2013

Ожидание 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());
11 голосов
/ 28 октября 2013

Начиная с раннего выпуска 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);
11 голосов
/ 24 июля 2014

Я добавлю 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 можно найти здесь .

7 голосов
/ 12 мая 2014

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

7 голосов
/ 23 сентября 2008

Настройка:

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();
  }
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...