Java List.contains(объект с значением поля, равным x)

Я хочу проверить, содержит ли List объект, у которого есть поле с определенным значением. Теперь я мог бы использовать цикл, чтобы пройти и проверить, но мне было любопытно, есть ли что-то более эффективное для кода.

Что-то вроде:

if(list.contains(new Object().setName("John"))){
    //Do some stuff
}

Я знаю, что приведенный выше код ничего не делает, он просто демонстрирует примерно то, что я пытаюсь достичь.

Кроме того, просто для того, чтобы уточнить, причина, по которой я не хочу использовать простой цикл, состоит в том, что этот код будет входить в цикл, который находится внутри цикла, который находится внутри цикла. Для удобства чтения я не хочу добавлять петли к этим циклам. Поэтому я задавался вопросом, существуют ли какие-либо простые (иш) альтернативы.

Ответ 1

Струйные

Если вы используете Java 8, возможно, вы можете попробовать что-то вроде этого:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().filter(o -> o.getName().equals(name)).findFirst().isPresent();
}

Или, альтернативно, вы можете попробовать что-то вроде этого:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().map(MyObject::getName).filter(name::equals).findFirst().isPresent();
}

Этот метод вернет true, если List<MyObject> содержит MyObject с именем name. Если вы хотите выполнить операцию над каждым из MyObject, что getName().equals(name), вы можете попробовать что-то вроде этого:

public void perform(final List<MyObject> list, final String name){
    return list.stream().filter(o -> o.getName().equals(name)).forEach(
            o -> {
                //...
            }
    );
}

Где o представляет экземпляр MyObject.

Ответ 2

У вас есть два варианта.

1. Первым выбором, который предпочтительнее, является переопределение метода `equals()` в вашем классе Object.

Скажем, например, у вас есть этот класс Object:

public class MyObject {
    private String name;
    private String location;
    //getters and setters
}

Теперь скажем, что вы только заботитесь о имени MyObject, что он должен быть уникальным, поэтому, если два `MyObject` имеют одинаковое имя, их следует считать равными. В этом случае вам нужно переопределить метод `equals()` (а также метод `hashcode()`), чтобы он сравнивал имена для определения равенства.

Как только вы это сделаете, вы можете проверить, содержит ли коллекция MyObject с именем "foo" следующим образом:

MyObject object = new MyObject();
object.setName("foo");
collection.contains(object);

Однако это может быть не для вас, если:

  • Вы используете имя и местоположение для проверки равенства, но вы хотите проверить, есть ли в коллекции какие-либо `MyObject` с определенным местоположением. В этом случае вы уже переопределили `equals()`.
  • `MyObject` является частью API, который вы не можете изменить.

Если это так, вам понадобится вариант 2:

2. Напишите свой собственный метод утилиты:

public static boolean containsLocation(Collection<MyObject> c, String location) {
    for(MyObject o : c) {
        if(o != null && o.getLocation.equals(location)) {
            return true;
        }
    }
    return false;
}

В качестве альтернативы вы можете расширить ArrayList (или некоторую другую коллекцию), а затем добавить к нему свой собственный метод:

public boolean containsLocation(String location) {
    for(MyObject o : this) {
        if(o != null && o.getLocation.equals(location)) {
                return true;
            }
        }
        return false;
    }

К сожалению, там нет лучшего способа.

Ответ 3

Google Guava

Если вы используете Guava, вы можете воспользоваться функциональным подходом и выполнить следующие

FluentIterable.from(list).find(new Predicate<MyObject>() {
   public boolean apply(MyObject input) {
      return "John".equals(input.getName());
   }
}).Any();

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

Как отмечено ниже, структура java.util.Stream, встроенная в Java 8 и более поздняя версия, обеспечивает нечто подобное.

Ответ 4

Двоичный поиск

Вы можете использовать Collections.binarySearch для поиска элемента в вашем списке (при условии, что список отсортирован):

Collections.binarySearch(list, new YourObject("a1", "b",
                "c"), new Comparator<YourObject>() {

            @Override
            public int compare(YourObject o1, YourObject o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

который вернет отрицательное число, если объект отсутствует в коллекции, иначе он вернет объект index объекта. С помощью этого вы можете искать объекты с различными стратегиями поиска.

Ответ 5

Collection.contains() реализуется путем вызова equals() для каждого объекта, пока не будет возвращен true.

Таким образом, один из способов реализовать это - переопределить equals(), но, конечно, вы можете иметь только одно значение.

Рамки вроде Guava поэтому используют предикаты для этого. С помощью Iterables.find(list, predicate) вы можете искать произвольные поля, поставив тест в предикат.

Другие языки, построенные на вершине виртуальной машины, встроены. В Groovy, например, вы просто пишете:

def result = list.find{ it.name == 'John' }

Java 8 упростила нашу жизнь:

List<Foo> result = list.stream()
    .filter(it -> "John".equals(it.getName())
    .collect(Collectors.toList());

Если вам нравятся такие вещи, я предлагаю книгу "Beyond Java". Он содержит много примеров для многочисленных недостатков Java и того, как лучше работают другие языки.

Ответ 6

Коллекции Eclipse

Если вы используете Eclipse Collections, вы можете использовать метод anySatisfy(). Либо адаптируйте List в ListAdapter, либо измените List на ListIterable, если это возможно.

ListIterable<MyObject> list = ...;

boolean result =
    list.anySatisfy(myObject -> myObject.getName().equals("John"));

Если вы будете делать такие операции часто, лучше извлечь метод, который отвечает, имеет ли тип атрибут.

public class MyObject
{
    private final String name;

    public MyObject(String name)
    {
        this.name = name;
    }

    public boolean named(String name)
    {
        return Objects.equals(this.name, name);
    }
}

Вы можете использовать альтернативную форму anySatisfyWith() вместе со ссылкой на метод.

boolean result = list.anySatisfyWith(MyObject::named, "John");

Если вы не можете изменить List на ListIterable, здесь вы можете использовать ListAdapter.

boolean result = 
    ListAdapter.adapt(list).anySatisfyWith(MyObject::named, "John");

Примечание. Я являюсь коммиттером для отражений Eclipse.

Ответ 7

Карта

Вы можете создать Hashmap<String, Object>, используя одно из значений в качестве ключа, а затем увидеть, если yourHashMap.keySet().contains(yourValue) возвращает значение true.

Ответ 8

contains метод использует equals внутренне. Поэтому вам необходимо переопределить метод equals для вашего класса в соответствии с вашими потребностями.

Btw это выглядит не корректно:

new Object().setName("John")

Ответ 9

Predicate

Если вы не используете Java 8 или библиотеку, которая дает вам больше возможностей для работы с коллекциями, вы можете реализовать что-то, что может быть более многоразовым, чем ваше решение.

interface Predicate<T>{
        boolean contains(T item);
    }

    static class CollectionUtil{

        public static <T> T find(final Collection<T> collection,final  Predicate<T> predicate){
            for (T item : collection){
                if (predicate.contains(item)){
                    return item;
                }
            }
            return null;
        }
    // and many more methods to deal with collection    
    }

Я использую что-то подобное, у меня есть предикатный интерфейс, и я передаю его реализацию в мой класс util.

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

если вы хотите его использовать, все, что вам нужно сделать, это метод вызова и определить предикат tyour

CollectionUtil.find(list, new Predicate<MyObject>{
    public boolean contains(T item){
        return "John".equals(item.getName());
     }
});

Ответ 10

Если вам нужно выполнить этот List.contains(Object with field value equal to x) несколько раз, простым и эффективным решением будет следующее:

List<field obj type> fieldOfInterestValues = new ArrayList<field obj type>;
for(Object obj : List) {
    fieldOfInterestValues.add(obj.getFieldOfInterest());
}

Тогда List.contains(Object with field value equal to x) будет иметь тот же результат, что и fieldOfInterestValues.contains(x);

Ответ 11

Несмотря на JAVA 8 SDK, существует множество библиотек инструментов сбора данных, которые помогут вам работать, например:  http://commons.apache.org/proper/commons-collections/

Predicate condition = new Predicate() {
   boolean evaluate(Object obj) {
        return ((Sample)obj).myField.equals("myVal");
   }
};
List result = CollectionUtils.select( list, condition );

Ответ 12

Вот решение, использующее Guava

private boolean checkUserListContainName(List<User> userList, final String targetName){

    return FluentIterable.from(userList).anyMatch(new Predicate<User>() {
        @Override
        public boolean apply(@Nullable User input) {
            return input.getName().equals(targetName);
        }
    });
}