Вопрос-ответ

How to filter a Java Collection (based on predicate)?

Как отфильтровать коллекцию Java (на основе предиката)?

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

Переведено автоматически
Ответ 1

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

Ответ 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));

ОБНОВЛЕНИЕ (после выпуска Java 8):

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

Ответ 3

Используйте CollectionUtils.filter (коллекция, предикат), из Apache Commons.

Ответ 4

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

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

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

java collections