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

199

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

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

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

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

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

Руди Кершоу
источник
5
Так как это пользовательское равенство, вам придется написать собственный код.
Сотириос Делиманолис
Ваша заявленная цель и пример кода не совпадают. Вы хотите сравнить объекты только на основе одного значения поля?
Дункан Джонс
1
Переопределить equals(Object)метод вашего пользовательского объекта?
Джош М
1
for(Person p:list) if (p.getName().equals("John") return true; return false;Боюсь, в Java вы не найдете более лаконичного способа.
MikeFHay
@Rajdeep извини, я не понимаю твой вопрос. всегда p.equals(p) должно быть правдой, поэтому я запутался в том, чего ты пытаешься достичь. Надеюсь, если вы зададите новый вопрос, вы сможете получить лучшую помощь.
MikeFHay

Ответы:

267

Streams

Если вы используете 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. Если вы хотите , чтобы выполнить операцию по каждому из MyObjectS , что getName().equals(name), то вы могли бы попробовать что - то вроде этого:

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

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

В качестве альтернативы, как предлагают комментарии (спасибо MK10), вы можете использовать Stream#anyMatchметод:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().equals(name));
}
Джош М
источник
1
Тогда второй пример должен быть public boolean. Кроме того, вы можете использовать Objects.equals()в случае, если o.getName()это возможно null.
Эрик Жаблоу
1
Я просто параноик программист. Я имел дело с проектами, где люди были быстрыми и свободными от нулей, поэтому я склонен защищаться. Намного лучше, чтобы предметы никогда не были нулевыми.
Эрик Жаблоу
5
@EricJablow Возможно, вам следует прокомментировать ВСЕ ответы, которые не выполняют nullпроверки, в отличие от одного (моего).
Джош М
55
Еще короче и проще для понимания: return list.stream().anyMatch(o -> o.getName().equals(name));
MK10
3
Я согласен с @ MK10, но я бы использовал java.util.Objectsдля сравнения с нулевым значением. return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Если вы хотите проверить объекты в потоке на наличие нулевых .filter(Objects::nonNull)значений , вы можете добавить их перед anyMatch.
2016 г.
81

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

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

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

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

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

Как только вы это сделаете, вы можете проверить, содержит ли Collection 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;
    }

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

Джеймс Данн
источник
Я думаю, что вы забыли скобки для получения, если (o! = Null && o.getLocation.equals (location))
olszi
25

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 и более поздние версии, предоставляет нечто подобное.

Брайан Агнью
источник
1
В качестве альтернативы вы можете использовать Iterables.any(list, predicate), что практически одинаково, и вы можете или не можете предпочесть его стилистически.
MikeFHay
@EricJablow Да. :) Вы можете посмотреть на мое решение.
Джош М
25

Вот как это сделать с помощью Java 8+:

boolean isJohnAlive = list.stream().anyMatch(o -> o.getName().equals("John"));
GabrielBB
источник
20

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

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

Аарон Дигулла
источник
Итак, если я переопределю метод equals пользовательских объектов, добавляемых в список ... могу ли я заставить его просто возвращать true, если определенные переменные класса совпадают?
Руди Кершоу
1
Нет, идея equals()очень ограничена. Это означает проверку «идентичности объекта» - что бы это ни значило для некоторого класса объектов. Если вы добавили флаг, какие поля должны быть включены, это может вызвать все виды проблем. Я настоятельно советую против этого.
Аарон Дигулла
Любите заводные, так что "новая" Java 8 Lambda действительно не дает нам эту крутость до сих пор? def result = list.find{ it.name == 'John' }Oracle / JCP должны быть привлечены к ответственности за военные преступления против программистов.
Кен
19

Бинарный поиск

Вы можете использовать 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объект. При этом вы можете искать объекты с различными стратегиями поиска.

Дебожит Сайкия
источник
Это отличное решение для небольших проектов, которые не хотят использовать гуаву. Однако один вопрос заключается в том, что документы по двоичному поиску гласят: «Список должен быть отсортирован в порядке возрастания в соответствии с указанным компаратором до выполнения этого вызова. Если он не отсортирован, результаты не определены». Но, кажется, в некоторых случаях, в зависимости от того, что сравнивается, это может быть не так?
Крисмаркс
и отрицательное число представляет, куда будет вставлен объект, если вы добавите его и выполните повторную сортировку.
Фиорентиноинг
8

карта

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


источник
+1 Несмотря на то, что при размещении деталей на карте требуется немного больше времени, вы получаете постоянный поиск по времени. Я удивлен, что никто не предложил это до сих пор.
Руди Кершоу
6

Коллекции Затмения

Если вы используете 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 ollections.

Крейг П. Мотлин
источник
5

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    
    }

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

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

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

CollectionUtil.find(list, new Predicate<MyObject>{
    public boolean contains(T item){
        return "John".equals(item.getName());
     }
});
user902383
источник
4

Вот решение с использованием гуавы

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);
        }
    });
}
Фан Ван Линь
источник
3

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

Кстати, это не выглядит синтетически правильно:

new Object().setName("John")
Джунед Асан
источник
3
Если только сеттер не вернется this.
Сотириос Делиманолис
Итак, если я переопределю метод equals пользовательских объектов, добавляемых в список ... могу ли я заставить его просто возвращать true, если определенные переменные класса совпадают?
Руди Кершоу
@RudiKershaw Да, это идея, вы можете вернуть true на основе одного / двух / всех полей. Поэтому, если вы считаете, что логически правильно сказать, что два объекта с одинаковыми именами являются равными объектами, верните true, просто сопоставив имена.
Джун Асан
1
Это действительно не рекомендуется переопределять equalsдля одного случая использования, если это не имеет смысла для класса в целом. Тебе было бы намного лучше просто написать цикл.
MikeFHay
@MikeFHay согласился, поэтому я упомянул в комментарии «Так что, если вы считаете, что логически правильно говорить, что два объекта с одинаковыми именами являются равными объектами, возвращайте истину»
Juned Ahsan
1

Если вам нужно выполнить это 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);

Магда
источник
1

Вы также можете anyMatch()использовать:

public boolean containsName(final List<MyObject> list, final String name){
    return list.stream().anyMatch(o -> o.getName().contains(name));
}
akshaymittal143
источник
0

Несмотря на 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 );
Паша Г.Р.
источник