Поиск всех объектов, которые имеют заданное свойство внутри коллекции

У меня есть какой-то сложный объект, например кошка, у которого есть много свойств, таких как возраст, любимая еда для кошек и так далее.

Группа кошек хранится в коллекции Java, и мне нужно найти всех кошек в возрасте 3 лет или тех, чья любимая еда для кошек - вискас. Конечно, я могу написать собственный метод, который находит этих кошек с определенным свойством, но это становится громоздким со многими свойствами; есть ли какой-то общий способ сделать это?

Ответов (20)

Решение

Вы можете написать метод, который принимает экземпляр интерфейса, который определяет check(Cat) метод, где этот метод может быть реализован с любой проверкой свойств, которую вы хотите.

А еще лучше сделать его общим:

public interface Checker<T> {
    public boolean check(T obj);
}

public class CatChecker implements Checker<Cat> {
    public boolean check(Cat cat) {
        return (cat.age == 3); // or whatever, implement your comparison here
    }
}

// put this in some class
public static <T> Collection<T> findAll(Collection<T> coll, Checker<T> chk) {
    LinkedList<T> l = new LinkedList<T>();
    for (T obj : coll) {
         if (chk.check(obj))
             l.add(obj);
    }
    return l;
}

Конечно, как говорят другие, это то, для чего были созданы реляционные базы данных ...

Похоже на то, что вы бы использовали LINQ в .NET.

Хотя «настоящей» реализации LINQ для java еще нет, вы можете взглянуть на Quaere, который может делать то, что вы описываете, достаточно легко.

Решения, основанные на предикатах, фильтрах и настраиваемых итераторах / компараторах, хороши, но они не предоставляют аналога с индексом базы данных. Например: я хотел бы искать в коллекции кошек разными способами: по полу и возрасту и по возрасту, чтобы она выглядела как два индекса: 1) [пол, возраст] 2) [возраст]. Значения, к которым осуществляется доступ с помощью этих индексов, могут быть хешированы для реализации быстрого поиска без повторения всей коллекции. Где-нибудь есть такое решение?

Когда дело доходит до такого рода проблем, Guava имеет очень мощные возможности поиска. Например, если в вашей местности выполняется поиск объекта на основе одного из его свойств, вы можете подумать:

Iterables.tryFind(listOfCats, new Predicate<Cat>(){
    @Override
    boolean apply(@Nullable Cat input) {
        return "tom".equalsIgnoreCase(input.name());
    }
}).or(new Cat("Tom"));

если возможно, что кота Тома нет в listOfCats, он будет возвращен, что позволит вам избежать NPE.

Используйте Google Guava.

final int lastSeq = myCollections.size();
Clazz target = Iterables.find(myCollections, new Predicate<Clazz>() {
    @Override
    public boolean apply(@Nullable Clazz input) {
      return input.getSeq() == lastSeq;
    }
});

Думаю воспользоваться этим методом.

С помощью лямбда-выражения Java 8 вы можете сделать что-то вроде

cats.stream()
    .filter( c -> c.getAge() == 3 && c.getFavoriteFood() == WHISKAS )
    .collect(Collectors.toList());

Концептуально такой же, как подход Guava Predicate, но выглядит намного чище с лямбда-выражением.

Вероятно, неправильный ответ для OP, но стоит отметить для людей с аналогичными потребностями. :)

вы можете искать элемент из списка с помощью следующей функции. удачи!

int _searchList(List<T> list,T item) {
int idx = -1;
idx = Collections.binarySearch(list,item,
        new Comparator<T>() {
            public int compare(Titm1,
                    Titm2) {

                // key1
                if (itm1.key1.compareTo(itm2.key1) != 0) {
                    return itm1.key2.compareTo(itm2.key2);
                }

                // key2
                return itm1.key2
                        .compareTo(itm2.key2);
            }
        });

return idx;

}

Вы можете использовать lambdaj . Подобные вещи тривиальны, синтаксис действительно гладкий:

Person me = new Person("Mario", "Fusco", 35);
Person luca = new Person("Luca", "Marrocco", 29);
Person biagio = new Person("Biagio", "Beatrice", 39);
Person celestino = new Person("Celestino", "Bellone", 29);
List<Person> meAndMyFriends = asList(me, luca, biagio, celestino);
List<Person> oldFriends = filter(having(on(Person.class).getAge(), greaterThan(30)), meAndMyFriends);

и вы можете делать гораздо более сложные вещи. Он использует Hamcrest для Матчеров. Некоторые будут утверждать, что это не в стиле Java, но забавно, как этот парень изменил Java, чтобы сделать немного функционального программирования. Взгляните также на исходный код, это довольно научно.

JFilter http://code.google.com/p/jfilter/ соответствует вашим требованиям.

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

Ключевая особенность

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

Использование коллекций Commons:

EqualPredicate nameEqlPredicate = new EqualPredicate(3);
BeanPredicate beanPredicate = new BeanPredicate("age", nameEqlPredicate);
return CollectionUtils.filter(cats, beanPredicate);

К вашему сведению, на этот вопрос есть еще 3 ответа, в которых используется Guava, но ни один из них не отвечает на этот вопрос. Спрашивающий сказал, что хочет найти всех кошек с подходящим свойством, например, возраст 3 года Iterables.find будет соответствовать только одному, если таковые существуют. Для этого вам нужно Iterables.filter будет использовать Guava, например:

Iterable<Cat> matches = Iterables.filter(cats, new Predicate<Cat>() {
    @Override
    public boolean apply(Cat input) {
        return input.getAge() == 3;
    }
});

Вы можете хранить эти объекты в базе данных. Если вам не нужны накладные расходы на полноценный сервер базы данных, вы можете использовать встроенный, например HSQLDB. Затем вы можете использовать Hibernate или BeanKeeper (проще в использовании) или другой ORM для сопоставления объектов с таблицами. Вы продолжаете использовать объектно-ориентированную модель и получаете преимущества расширенного хранилища и запросов от базы данных.

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

Вы можете пойти дальше и сохранить имена свойств, возможно, на карте с желаемыми значениями. В этом случае вы должны использовать отражение в классе Cat для вызова соответствующих методов по именам свойств.

public class CatSearcher {

  private Integer ageToFind = null;
  private String  foodToFind = null;

  public CatSearcher( Integer age, String food ) {
    this.ageToFind = age;
    this.foodToFind = food;
  }

  private boolean isMatch(Cat cat) {
    if ( this.ageToFind != null && !cat.getAge().equals(this.ageToFind) ) {
       return false;
    }
    if ( this.foodToFind != null && !cat.getFood().equals(this.foodToFind) {
       return false;
    }
    return true;
  }

  public Collection<Cat> search( Collection<Cat> listToSearch ) {
    // details left to the imagination, but basically iterate over
    // the input list, call isMatch on each element, and if true
    // add it to a local collection which is returned at the end.
  }

}

Я предлагаю использовать Jxpath , он позволяет выполнять запросы к графам объектов, как если бы он был где xpath, например

JXPathContext.newContext(cats).
     getValue("//*[@drinks='milk']")

Вы можете использовать что-то вроде JoSQL и написать SQL для своих коллекций: http://josql.sourceforge.net/

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

Вы можете попробовать общий код проекта Apache Commons. Коллекции субпроект предоставляет код для поиска объектов , которые соответствуют конкретному предикату, а также большое количество предикатов (равно, NULL, InstanceOf и т.д.). BeanUtils подпроект позволяет сделать предикаты , что тест свойства бобов.

Используйте класс CollectionUtils для поиска в коллекции. Для этого есть несколько методов, но, в частности, обратите внимание на метод select (). Используйте следующие классы для создания предикатов или напишите свои собственные: Predicate , PredicateUtils , BeanPredicate .

Иногда это все немного громоздко, но, по крайней мере, это универсально! :-)

Я использую Google Collections (теперь называемые Guava ) для такого рода проблем. Существует класс Iterables, который может принимать интерфейс Predicate в качестве параметра действительно полезного метода.

Cat theOne = Iterables.find(cats, new Predicate<Cat>() {
    public boolean apply(Cat arg) { return arg.age() == 3; }
});

Проверьте это здесь !

Попробуйте API общих коллекций:

List<Cat> bigList = ....; // master list

Collection<Cat> smallList = CollectionUtils.select(bigList, new Predicate() {
    public boolean evaluate(Object o) {
        Cat c = (Cat)o;
        return c.getFavoriteFood().equals("Wiskas") 
            && c.getWhateverElse().equals(Something);
    }
});

Конечно, вам не нужно каждый раз использовать анонимный класс , вы можете создавать реализации Predicate интерфейса для часто используемых поисков.

Опять же, с API общих коллекций: вы получаете код типа «проверочного», когда реализуете предикат отдельно: -

public class CatPredicate implements Predicate {

    private int age; 


    public CatPredicate(int age) {
        super();
        this.age = age;
    }


    @Override
    public boolean evaluate(Object o) {
        Cat c (Cat)o;
        return c.getAge()==this.age;
    }

}

который используется как: -

CollectionUtils.filter(catCollectionToFilter, new CatPredicate(3))

Очень распространенная проблема, и я использовал коллекции Google, и вот мой код

public class FindByIdPredicate implements Predicate<IDObject> {

private Long entityId;

public FindByIdPredicate(final Long entityId) {
    this.entityId = entityId;

}

@Override
public boolean apply(final IDObject input) {
    return input.getId().equals(this.entityId);
}

/**
     * Pass in the Collection
 * @param Collection
 * @return IdObject if present or null
 */
public IDObject getEntity(final Collection<? extends IDObject> collection) {

    for (IDObject idObject : collection) {
        if (this.apply(idObject)) {
            return idObject;
        }
    }
    return null;

}

/**
 * @param Set
 * @return IdObject if present or null
 */
@SuppressWarnings("unchecked")
public <T> T getEntity(final Set<? extends IDObject> set) {

    for (IDObject idObject : set) {
        if (this.apply(idObject)) {
            return (T) idObject;
        }
    }
    return null;

}

}

Надеюсь это поможет