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

Потоки

Якщо ви використовуєте 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>містить a 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для порівняння nullsafe return list.stream().anyMatch(o -> Objects.euqals(o.getName(), name);Якщо ви хочете перевірити об'єкти в потоці на null, ви можете додати .filter(Objects::nonNull)до anyMatch.
tobijdc

81

У вас є два варіанти.

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;
    }

На жаль, немає кращого шляху.


Я думаю, ви забули дужки для getter in if (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 Yup :) Ви могли поглянути на моє рішення.
Джош М

25

Ось як це зробити за допомогою Java 8+:

boolean isJohnAlive = list.stream().anyMatch(o -> o.getName().equals("John"));

20

Collection.contains()реалізується за допомогою виклику equals()кожного об'єкта, поки один не повернеться true.

Отже, один із способів цього здійснити - це перекриття, equals()але, звичайно, ви можете мати лише одне рівне.

Тому такі структури, як Guava, використовують для цього предикати. З Iterables.find(list, predicate), ви можете шукати довільні поля, помістивши тест у предикат.

Інші мови, побудовані на вершині VM, вбудовані в цю програму. Наприклад, у 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 та того, як інші мови роблять краще.


Отже, якщо я замінюю метод рівних у користувальницьких об'єктах, що додаються до списку ... чи можу я змусити його повернути правду, якщо певні змінні класу однакові?
Руді Кершо

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об'єкт. За допомогою цього ви можете шукати об’єкти з різними стратегіями пошуку.


Це чудове рішення для невеликих проектів, які не хочуть використовувати гуаву. Одне питання, однак, в документах на binarySearch зазначається, що: "Список повинен бути відсортований у порядку зростання відповідно до зазначеного компаратора, перш ніж робити цей виклик. Якщо він не відсортований, результати не визначені". Але, здається, в деяких випадках, в залежності від того, що порівнюється, це може бути не так?
chrismarx

і від’ємне число відображає, куди буде вставлений об’єкт, якщо ви додасте його та пересортуєте.
fiorentinoing

8

Карта

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


+1 Хоча це означає, що в першу чергу викладаєте деталі на карту, ви отримуєте постійний пошук часу. Я здивований, що ніхто до цього часу не пропонує.
Руді Кершо

6

Колекції затемнення

Якщо ви використовуєте Eclipse Collection , ви можете скористатися anySatisfy()методом. Або адаптуйте своїList в a, ListAdapterабо по можливості замініть свій Listна a 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.


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    
    }

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

Яка перевага робити це на моєму шляху? у вас є один метод, який стосується пошуку в будь-якій колекції типів. і вам не потрібно створювати окремі методи, якщо ви хочете здійснювати пошук за різними полями. Все, що вам потрібно зробити, це надати різні предикати, які можна знищити, як тільки він більше не буде корисним /

якщо ви хочете ним скористатися, все, що вам потрібно зробити, це метод виклику та визначення свого предиката

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

4

Ось рішення з використанням 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);
        }
    });
}

3

containsметод використовується equalsвнутрішньо. Тож вам потрібно перекрити equalsметод для вашого класу відповідно до ваших потреб.

До речі, це не виглядає синтаксично правильним:

new Object().setName("John")

3
Якщо сетер не повернеться this.
Сотіріос Деліманоліс

Отже, якщо я замінюю метод рівних у користувальницьких об'єктах, що додаються до списку ... чи можу я змусити його повернути правду, якщо певні змінні класу однакові?
Руді Кершо

@RudiKershaw Так, це ідея, ви можете повернути істину на основі одного / двох / усіх полів. Тож якщо ви вважаєте, що логічно правильно сказати, що два об’єкти з однаковою назвою є рівними об'єктами, то повертайте істину, просто склавши імена.
Juned Ahsan

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

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 );
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.