Сравните две коллекции 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.
Ответы
Ответ 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;
}
Затем, когда вы найдете дополнительные классы, которые нужно взвешивать и сравнивать, вы можете поместить их в свою коллекцию, и их можно сравнить друг с другом. Просто идея.