Сравните две коллекции Java с помощью Comparator вместо equals() - программирование
Подтвердить что ты не робот

Сравните две коллекции Java с помощью Comparator вместо equals()

Заявление о проблемах

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

public class Name {
    private String name;
    private int weightedRank;

    //getters & setters

    @Override
    public boolean equals(Object obj) {
        return this.name.equals(obj.name); //Naive implementation just to show
                                           //equals is based on the name field.
    }
}

Я хочу сравнить две коллекции, чтобы утверждать, что для позиции i в каждой коллекции weightedRank каждого имени в этой позиции имеет одно и то же значение. Я сделал некоторый Googling, но не нашел подходящего метода в Commons Collections или любом другом API, поэтому я придумал следующее:

public <T> boolean comparatorEquals(Collection<T> col1, Collection<T> col2,
        Comparator<T> c)
{
    if (col1 == null)
        return col2 == null;
    if (col2 == null) 
        return false;

    if (col1.size() != col2.size())
        return false;

    Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();

    while(i1.hasNext() && i2.hasNext()) {
        if (c.compare(i1.next(), i2.next()) != 0) {
            return false;
        }
    }

    return true;
}

Вопрос

Есть ли другой способ сделать это? Я пропустил очевидный метод из Коллекций Commons?

Связанные

Я также заметил этот вопрос на SO, который похож, хотя в этом случае я думаю, что переопределение equals() имеет немного больше смысла.

Изменить

Что-то очень похожее на то, что в ближайшее время (на момент написания этой статьи) будет выпущен Apache Commons Collections, См. https://issues.apache.org/jira/browse/COLLECTIONS-446.

4b9b3361

Ответ 1

Я не уверен, что этот способ на самом деле лучше, но это "другой способ"...

Возьмите свои оригинальные две коллекции и создайте новые, содержащие Адаптер для каждого базового объекта. Адаптер должен иметь .equals() и .hashCode(), реализованный как основанный на Name.calculateWeightedRank(). Затем вы можете использовать нормальное соответствие коллекции для сравнения коллекций адаптеров.

* Изменить *

Использование стандартного хеш-кода Eclipse/equals для Adapter. Ваш код просто вызовет adaptCollection для каждой из ваших базовых наборов, а затем List.equals() два результата.

public class Adapter {

    public List<Adapter> adaptCollection(List<Name> names) {
        List<Adapter> adapters = new ArrayList<Adapter>(names.size());

        for (Name name : names) {
            adapters.add(new Adapter(name));
        }

        return adapters;
    }


    private final int name;

    public Adapter(Name name) {
        this.name = name.getWeightedResult();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + name;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Adapter other = (Adapter) obj;
        if (name != other.name)
            return false;
        return true;
    }

}

Ответ 2

Вы можете использовать класс Guava Equivalence, чтобы отделить понятия "сравнение" и "эквивалентность". Вам все равно придется написать свой метод сравнения (AFAIK Guava не имеет его), который принимает подкласс Equivalence вместо Comparator, но по крайней мере ваш код будет менее запутанным, и вы можете сравнить свои коллекции на основе любых критериев эквивалентности.

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

Ответ 3

Вы можете использовать новый метод isEqualCollection, добавленный в CollectionUtils, начиная с версии 4. Этот метод использует внешний механизм сравнения, обеспечиваемый реализацией интерфейса Equator. Пожалуйста, проверьте этот javadocs: CollectionUtils.isEqualCollection(...) и Equator.

Ответ 4

EDIT. Удален старый ответ.

Другим вариантом, который у вас есть, является создание интерфейса под названием Weighted, который может выглядеть следующим образом:

public interface Weighted {
    int getWeightedRank();
}

Затем ваш класс Name реализует этот интерфейс. Затем вы можете изменить свой метод, чтобы он выглядел следующим образом:

 public <T extends Weighted> boolean weightedEquals(Collection<T> col1, Collection<T> col2)
{
    if (col1 == null)
      return col2 == null;
     if (col2 == null) 
      return false;

  if (col1.size() != col2.size())
      return false;

  Iterator<T> i1 = col1.iterator(), i2 = col2.iterator();

  while(i1.hasNext() && i2.hasNext()) {
      if (i1.next().getWeightedRank() != i2.next().getWeightedRank()) {
          return false;
      }
  }

  return true;
}

Затем, когда вы найдете дополнительные классы, которые нужно взвешивать и сравнивать, вы можете поместить их в свою коллекцию, и их можно сравнить друг с другом. Просто идея.