Наиболее эффективный способ узнать, содержит ли ArrayList объект в Java


У меня есть ArrayList объектов в Java. Объекты имеют четыре поля, два из которых я бы использовал, чтобы считать объект равным другому. Я ищу наиболее эффективный способ, учитывая эти два поля, чтобы увидеть, содержит ли массив этот объект.

ключ заключается в том, что эти классы генерируются на основе объектов XSD, поэтому я не могу изменить сами классы, чтобы перезаписать .equals.

есть ли лучший способ, чем просто зацикливание и ручное сравнение два поля для каждого объекта, а затем нарушение при обнаружении? Это просто кажется таким грязным, ищет лучший способ.

Edit: ArrayList поступает из ответа SOAP, который не разбит на объекты.

12 68

12 ответов:

Это зависит от того, насколько эффективно вы должны быть. Просто повторяя список, ища элемент, который удовлетворяет определенному условию, является O (n), но так же и ArrayList.Содержит, если можно реализовать метод Equals. Если вы не делаете это в циклах или внутренних петлях, этот подход, вероятно, просто прекрасен.

Если вам действительно нужны очень эффективные скорости поиска любой ценой, вам нужно будет сделать две вещи:

  1. обойти тот факт, что класс есть сгенерировано: напишите класс адаптера, который можно обернуть сгенерированный класс и которые реализуют равна() на основе на этих двух полях (предполагая, что они являются публичными). Не забудьте также реализовать hashCode () (*)
  2. заверните каждый объект с этим адаптером и поместите его в хэш-набор. HashSet.содержит() имеет постоянное время доступа, т. е. O(1) вместо O (n).

конечно, создание этого хэш-набора по-прежнему имеет стоимость O(n). Вы только собираетесь чтобы получить что-либо, если стоимость построения HashSet незначительна по сравнению с общей стоимостью всех проверок contains (), которые вам нужно сделать. Попытка построить список без дубликатов-это такой случай.


* () реализация hashCode () лучше всего выполняется xor'ING (^operator) хэш-коды тех же полей, которые вы используете для реализации equals (но умножить на 31 чтобы уменьшить вероятность выхода XOR 0)

вы можете использовать компаратор со встроенными методами Java для сортировки и двоичного поиска. Предположим, у вас есть такой класс, где A и b-поля, которые вы хотите использовать для сортировки:

class Thing { String a, b, c, d; }

вы бы определили свой компаратор:

Comparator<Thing> comparator = new Comparator<Thing>() {
  public int compare(Thing o1, Thing o2) {
    if (o1.a.equals(o2.a)) {
      return o1.b.compareTo(o2.b);
    }
    return o1.a.compareTo(o2.a);
  }
};

затем отсортируйте свой список:

Collections.sort(list, comparator);

и, наконец, сделать двоичный поиск:

int i = Collections.binarySearch(list, thingToFind, comparator);

учитывая ваши ограничения, вы застряли с поиском грубой силы (или создаете индекс, если поиск будет повторен). Можете ли вы подробно рассказать о том, как ArrayList генерируется--возможно, там есть какая-то комната для маневра.

Если все, что вы ищете, это более красивый код, рассмотрите возможность использования классов коллекций Apache Commons, В частности CollectionUtils.найти(), для готового синтаксического сахара:

ArrayList haystack = // ...
final Object needleField1 = // ...
final Object needleField2 = // ...

Object found = CollectionUtils.find(haystack, new Predicate() {
   public boolean evaluate(Object input) {
      return needleField1.equals(input.field1) && 
             needleField2.equals(input.field2);
   }
});

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

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

даже если метод equals были сравнивая эти два поля, то логически, это будет точно такой же код, как вы делаете это вручную. Хорошо, это может быть "грязно", но это все еще правильный ответ

Если вы являетесь пользователем my ForEach DSL, Это можно сделать с помощью Detect запрос.

Foo foo = ...
Detect<Foo> query = Detect.from(list);
for (Detect<Foo> each: query) 
    each.yield = each.element.a == foo.a && each.element.b == foo.b;
return query.result();

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

если ваша забота ремонтопригодность, то вы смогли сделать что Фабиан Штеге предложите ( это то, что я бы сделал), хотя это, вероятно, не самый "эффективный" (потому что вам нужно сначала отсортировать массив, а затем выполнить двоичный поиск ) но, безусловно, самый чистый и лучший вариант.

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

затем вы должны изменить место, где вы заполняете данные из ArrayList в YourCustomList.

как:

 List list = new ArrayList();

 fillFromSoap( list );

To:

 List list = new MyCustomSpecialList();

 fillFromSoap( list );

реализация будет что-то например:

class MyCustomSpecialList extends AbstractList  { 
    private Map<Integer, YourObject> internalMap;

    public boolean add( YourObject o ) { 
         internalMap.put( o.getThatFieldYouKnow(), o );
    }

    public boolean contains( YourObject o ) { 
        return internalMap.containsKey( o.getThatFieldYouKnow() );
    }

}

в значительной степени как HashSet, проблема здесь заключается в том, что HashSet полагается на хорошую реализацию метода hashCode, которого, вероятно, у вас нет. Вместо этого вы используете в качестве хэша "то поле, которое вы знаете", которое делает один объект равным другому.

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

расскажите, что вы сделали в конце.

может быть, список-это не то, что вам нужно.

может быть a TreeSet было бы лучше контейнер. Вы получаете o(log N) вставку и извлечение, а также упорядоченную итерацию (но не допускает дубликатов).

LinkedHashMap может быть даже лучше для вашего случая использования, проверьте это тоже.

построение хэш-карты этих объектов на основе значения поля в качестве ключа может быть полезным с точки зрения производительности, например, заполнить карты один раз и найти объекты очень эффективно

Если вам нужно искать много раз в одном списке, это может окупиться, чтобы построить индекс.

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

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

есть три основных варианта:

1) Если производительность извлечения имеет первостепенное значение, и это практично, используйте форму хэш-таблицы, построенную один раз (и измененную как/если список изменяется).

2) Если список удобно отсортирован или его удобно сортировать и o(log n) поиск достаточен, сортировка и поиск.

3) Если o (n) извлечение достаточно быстро или если нецелесообразно манипулировать/поддерживать структуру данных или альтернативу, повторите Список.

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

  • Почему нужно что-то другое? (Время) выполнения? Элегантность? Ремонтопригодность? Повторное использование? Все эти причины хороши, отдельно или вместе, но они влияют на решение.

  • насколько вы контролируете структуру данных, о которой идет речь? Можете ли вы повлиять на то, как он построен? Управляемый позже?

  • каков жизненный цикл структуры данных (и базовых объектов)? Он создан сразу и никогда не меняется, или очень динамичен? Может ли ваш код контролировать (или даже изменять) свой жизненный цикл?

  • есть и другие важные ограничения, такие как объем памяти? Имеет ли значение информация о дубликатах? Так далее.

Я бы сказал, что самым простым решением было бы обернуть объект и делегировать вызов contains коллекции обернутого класса. Это похоже на компаратор, но не заставляет вас сортировать полученную коллекцию, вы можете просто использовать ArrayList.содержит.)(

public class Widget {
        private String name;
        private String desc;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getDesc() {
            return desc;
        }

        public void setDesc(String desc) {
            this.desc = desc;
        }
    }



    public abstract class EqualsHashcodeEnforcer<T> {

        protected T wrapped;

        public T getWrappedObject() {
            return wrapped;
        }

        @Override
        public boolean equals(Object obj) {
            return equalsDelegate(obj);
        }

        @Override
        public int hashCode() {
            return hashCodeDelegate();
        }

        protected abstract boolean equalsDelegate(Object obj);

        protected abstract int hashCodeDelegate();
    }


    public class WrappedWidget extends EqualsHashcodeEnforcer<Widget> {

        @Override
        protected boolean equalsDelegate(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj == getWrappedObject()) {
                return true;
            }
            if (obj.getClass() != getWrappedObject().getClass()) {
                return false;
            }
            Widget rhs = (Widget) obj;

            return new EqualsBuilder().append(getWrappedObject().getName(),
                    rhs.getName()).append(getWrappedObject().getDesc(),
                    rhs.getDesc()).isEquals();
        }

        @Override
        protected int hashCodeDelegate() {

            return new HashCodeBuilder(121, 991).append(
                    getWrappedObject().getName()).append(
                    getWrappedObject().getDesc()).toHashCode();
        }

    }