Как сравнить две коллекции для "эквивалентности" на основе полей из разных классов Java?
учитывая любые два класса, например ClassA
и ClassB
ниже:
class ClassA {
private int intA;
private String strA;
private boolean boolA;
// Constructor
public ClassA (int intA, String strA, boolean boolA) {
this.intA = intA; this.strA = strA; this.boolA = boolA;
} // Getters and setters etc. below...
}
class ClassB {
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB (int intB, String strB, boolean boolB) {
this.intB = intB; this.strB = strB; this.boolB = boolB;
} // Getters and setters etc. below...
}
и любые два разных Collection
типа, один с ClassA
элементы и другие с ClassB
элементы, электронные.г:
List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<Object> mySet = new HashSet<Object>(
Arrays.asList(new ClassB(1, "A", false),
new ClassB(2, "B", false)));
каков самый простой способ сказать, являются ли эти два Collection
s являются "эквивалентными" (*) в терминах указанного подмножества полей?
(*) слово "эквивалент" используется вместо "равно", поскольку это контекстуальное, т. е. такая "эквивалентность" может быть определена иначе в другом контексте.
работал пример сверху:
Предположим, мы уточним, что intA
и strA
должно совпадать с intB
и strB
соответственно (а boolA
/ boolB
значения можно игнорировать). Это сделало бы два объекта коллекции, определенные выше, эквивалентными , но если элемент был добавлен или удален из одной из коллекций, они больше не будут быть.
предпочтительное решение: используемый метод должен быть общим для любого Collection
тип. В идеале Java 7 Как я ограничен использованием этого (но Java 8 может представлять дополнительный интерес для других). Рад использовать Guava или Apache Commons, но предпочел бы не использовать более неясные внешние библиотеки.
13 ответов
вот версия Java 8 с использованием lambdas и функций более высокого порядка. Вероятно, можно преобразовать это в Java 7, используя анонимные внутренние классы вместо lambdas. (Я считаю, что большинство IDEs имеют операцию рефакторинга, которая делает это. Я оставлю это упражнение для заинтересованных читателей.
на самом деле здесь есть две разные проблемы:
учитывая два объекта разных типов, оцените их, изучив соответствующие поля каждого. Этот отличается от операций" equals "и" compare", которые уже определены API библиотеки JDK, поэтому вместо этого я буду использовать термин" эквивалент".
учитывая две коллекции, содержащие элементы этих типов, Определите, являются ли они "равными" для некоторого определения этого термина. Это на самом деле довольно тонко; см. обсуждение ниже.
1. Эквивалентность
даны два объекта типа T
и U
мы хотим определить, эквивалентны ли они. В результате получается логическое значение. Это может быть представлено функцией типа BiPredicate<T,U>
. Но мы не можем исследовать объекты напрямую; вместо этого нам нужно извлечь соответствующие поля из каждого объекта и оценить результаты извлечения друг против друга. Если поле извлечено из T
типа TR
и поле, извлеченное из U
типа UR
, то экстракторы представлены функцией типы
Function<T, TR>
Function<U, UR>
теперь мы извлекли результаты типа TR
и UR
. Мы могли бы просто позвонить equals()
на них, но это излишне ограничительным. Вместо этого мы можем предоставить другую функцию эквивалентности, которая будет вызвана для оценки этих двух результатов друг против друга. Это BiPredicate<TR,UR>
.
учитывая все это, мы можем написать функцию более высокого порядка, которая принимает все эти функции и производит и функцию эквивалентности для нас (подстановочные знаки включены для полнота):
static <T,U,TR,UR> BiPredicate<T,U> equiv(Function<? super T, TR> tf,
Function<? super U, UR> uf,
BiPredicate<? super TR, ? super UR> pred) {
return (t, u) -> pred.test(tf.apply(t), uf.apply(u));
}
это, вероятно, общий случай для результатов извлечения поля, которые будут оцениваться с помощью equals()
, поэтому мы можем обеспечить перегрузку для этого:
static <T,U> BiPredicate<T,U> equiv(Function<? super T, ?> tf,
Function<? super U, ?> uf) {
return (t, u) -> equiv(tf, uf, Object::equals).test(t, u);
}
я мог бы предоставить другую переменную типа R
в результате тип обеих функций, чтобы убедиться, что они одного типа, но оказывается, что это не обязательно. С equals()
определен Object
и это Object
аргумент, нам на самом деле все равно, что возвращает функция типы, следовательно, подстановочные знаки.
вот как использовать это для оценки классов примеров OP, используя только строковые поля:
ClassA a = ... ;
ClassB b = ... ;
if (equiv(ClassA::getStrA, ClassB::getStrB).test(a, b)) {
// they're equivalent
}
в качестве вариации нам также может понадобиться примитивная специализация, чтобы избежать ненужного бокса:
static <T,U> BiPredicate<T,U> equivInt(ToIntFunction<? super T> tf,
ToIntFunction<? super U> uf) {
return (t, u) -> tf.applyAsInt(t) == uf.applyAsInt(u);
}
это позволяет нам строить функции эквивалентности на основе одного поля. Что, если мы хотим оценить эквивалентность на основе нескольких полей? Мы можем объединить произвольное число Бипредикатов путем сцепления the and()
метод. Вот как создать функцию, которая оценивает эквивалентность с помощью int
и String
поля классов из примера OP. Для этого, вероятно, лучше хранить функцию в переменной отдельно от ее использования, хотя, вероятно, все это может быть встроено (что, я думаю, сделает его нечитаемым):
BiPredicate<ClassA, ClassB> abEquiv =
equivInt(ClassA::getIntA, ClassB::getIntB)
.and(equiv(ClassA::getStrA, ClassB::getStrB));
if (abEquiv.test(a, b)) {
// they're equivalent
}
в качестве последнего примера, это довольно мощный, чтобы иметь возможность обеспечить функцию эквивалентности для результатов извлечения поля при создании функция эквивалентности для двух классов. Например, предположим, что мы хотим извлечь два строковых поля и считать их эквивалентными, если извлеченные строки равны, игнорируя регистр. Следующий код приводит к true
:
equiv(ClassA::getStrA, ClassB::getStrB, String::equalsIgnoreCase)
.test(new ClassA(2, "foo", true),
new ClassB(3, "FOO", false))
2. Коллекция "Равенство"
вторая часть-оценить, являются ли две коллекции "равными" в некотором смысле. Проблема в том, что в рамках коллекций, понятие равенства определяется таким образом, что список может быть равен только другому списку, а набор может быть равен только другому набору. Из этого следует, что коллекция другого типа никогда не может быть равна списку или набору. См. спецификацию Collection.equals()
для обсуждения этого вопроса.
это явно противоречит тому, чего хочет ОП. Как было предложено ОП, мы на самом деле не хотим "равенства", но мы хотим какое-то другое свойство, для которого нам нужно дать определение. На основе примеров OP и некоторых предложения в других ответах по Пшемек Gumula и Янош, похоже, мы хотим, чтобы элементы в двух коллекциях каким-то образом находились в соответствии один к одному. Я назову это биекция что может быть не математически точным, но кажется достаточно близким. Кроме того, соответствие между каждой парой элементов должно быть эквивалентности как определено выше.
вычисление это немного тонко, так как у нас есть наше собственное отношение эквивалентности. Мы не можем использовать многие встроенные операции коллекций, так как все они используют equals()
. Моя первая попытка была такова:--59-->
// INCORRECT
static <T,U> boolean isBijection(Collection<T> c1,
Collection<U> c2,
BiPredicate<? super T, ? super U> pred) {
return c1.size() == c2.size() &&
c1.stream().allMatch(t -> c2.stream()
.anyMatch(u -> pred.test(t, u)));
}
(это по существу то же самое, что и Пшемек Gumula.) Это имеет проблемы, которые сводятся к возможности более чем одного элемента в одной коллекции, соответствующего одному элементу в другой коллекции, оставляя элементы непревзойденными. Это дает странные результаты, если учитывая два мультинабора, используя равенство в качестве функции эквивалентности:
{a x 2, b} // essentially {a, a, b}
{a, b x 2} // essentially {a, b, b}
эта функция считает эти два мультинабора биекцией, что явно не так. Другая проблема возникает, если функция эквивалентности позволяет сопоставлять многие к одному:
Set<String> set1 = new HashSet<>(Arrays.asList("foo", "FOO", "bar"));
Set<String> set2 = new HashSet<>(Arrays.asList("fOo", "bar", "quux"));
isBijection(set1, set2, equiv(s -> s, s -> s, String::equalsIgnoreCase))
результат true
, но если множества заданы в обратном порядке, результат будет false
. Это явно неправильно.
альтернативным алгоритмом является создание временной структуры и удаляйте элементы по мере их соответствия. Структура должна учитывать дубликаты, поэтому нам нужно уменьшить количество и удалить элемент только тогда, когда количество достигнет нуля. К счастью, различные функции Java 8 делают это довольно простым. Это очень похоже на алгоритм, используемый в ответе от Янош, хотя я извлек функцию эквивалентности в параметр метода. Увы, поскольку моя функция эквивалентности может иметь вложенные функции эквивалентности, это означает, что я не могу зондировать карта (которая определяется равенством). Вместо этого я должен искать ключи карты, что означает, что алгоритм O(N^2). Ну что ж.
код, однако, довольно прост. Во-первых, частотная карта генерируется из второй коллекции с помощью groupingBy
. Затем элементы первой коллекции повторяются, а ключи частотной карты ищут эквивалент. Если один найден, его количество случаев уменьшается. Обратите внимание на возвращаемое значение null
от переназначения функция передана в Map.compute()
. Это имеет побочный эффект удаление запись, не устанавливая отображение в null
. Это немного взломать API, но это довольно эффективно.
для каждого элемента в первой коллекции должен быть найден эквивалентный элемент во второй коллекции, иначе он выпрыгивает. После того, как все элементы первой коллекции были обработаны, все элементы из частотной карты также должны быть обработаны, поэтому просто проверено на пустоту.
вот код:
static <T,U> boolean isBijection(Collection<T> c1,
Collection<U> c2,
BiPredicate<? super T, ? super U> pred) {
Map<U, Long> freq = c2.stream()
.collect(Collectors.groupingBy(u -> u, Collectors.counting()));
for (T t : c1) {
Optional<U> ou = freq.keySet()
.stream()
.filter(u -> pred.test(t, u))
.findAny();
if (ou.isPresent()) {
freq.compute(ou.get(), (u, c) -> c == 1L ? null : c - 1L);
} else {
return false;
}
}
return freq.isEmpty();
}
не совсем ясно, является ли это определение правильным. Но интуитивно кажется, что это то, чего хотят люди. Но она хрупкая. Если функция эквивалентности не симметрична,isBijection
не удастся. Есть также некоторые степени свободы не учитываются. Например, предположим, что коллекции
{a, b}
{x, y}
и a
эквивалентно обоим x
и y
, но b
только эквивалентно x
. Если a
соответствует x
, результат isBijection
is false
. Но если ... --42--> были согласованы с y
результат будет true
.
вместе
вот пример OP, закодированный с помощью equiv()
, equivInt()
и isBijection
функции:
List<ClassA> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<ClassB> mySet = new HashSet<>(Arrays.asList(new ClassB(1, "A", false),
new ClassB(2, "B", false)));
BiPredicate<ClassA, ClassB> abEquiv =
equivInt(ClassA::getIntA, ClassB::getIntB)
.and(equiv(ClassA::getStrA, ClassB::getStrB));
isBijection(myList, mySet, abEquiv)
в результате true
.
другим возможным решением является написание простого метода сравнения с предикатом (поэтому вы можете явно указать условие, чтобы два класса были похожи на ваши термины). Я создал это в Java 8:
<T, U> boolean compareCollections(Collection<T> coll1, Collection<U> coll2, BiPredicate<T, U> predicate) {
return coll1.size() == coll2.size()
&& coll1.stream().allMatch(
coll1Item -> coll2.stream().anyMatch(col2Item -> predicate.test(coll1Item, col2Item))
);
}
Как вы можете видеть, он сравнивает размер, а затем проверяет, имеет ли каждый элемент в коллекции аналог во второй коллекции (это не сравнение порядка). Он находится в Java 8, но вы можете перенести его на Java 7, реализовав простой Двухпредикатный код, allMatch и anyMatch (одного for-loop для каждого из них должно быть достаточно)
Edit: Java 7 код:
<T, U> boolean compareCollections(Collection<T> coll1, Collection<U> coll2, BiPredicate<T, U> predicate) {
if (coll1.size() != coll2.size()) {
return false;
}
for (T item1 : coll1) {
boolean matched = false;
for (U item2 : coll2) {
if (predicate.test(item1, item2)) {
matched = true;
}
}
if (!matched) {
return false;
}
}
return true;
}}
interface BiPredicate <T, U> {
boolean test(T t, U u);
}
здесь пример использования.
нет очень простого способа.
наиболее общим, который будет работать с обычными коллекциями Java, будет создание класса-оболочки, который будет принимать либо ClassA
или ClassB
в качестве ввода, а затем переопределить equals / hashcode, как определено вами.
в некоторых случаях вы можете злоупотреблять Comparator
, но это ограничило бы вас TreeMap/TreeSet
.
Вы также можете реализовать equals()
метод работы так, что classA.equals(classB);
возвращает true, но это может вызвать сложные ошибки, если вы не будете осторожны. Он может также привести к интересным ситуациям, где a.equals(b)
и b.equals(c)
но !a.equals(c)
.
некоторая библиотека (гуава?) также имел Comparator
механизм стиля для тестирования равенства, но это будет работать только с коллекциями библиотеки.
Apache Commons Lang имеет EqualsBuilder#reflectionEquals(Object, Object)
:
этот метод использует отражение, чтобы определить, если два
Object
s равны.Он использует
AccessibleObject.setAccessible
чтобы получить доступ к частной поля. Это означает, что при запуске будет выдано исключение безопасности в диспетчере безопасности, если разрешения настроены неправильно. Это также не так эффективно, как явное тестирование. Непримитивный поля сравниваются с помощьюequals()
.переходные члены не будут проверены, поскольку они, вероятно, получены поля, а не часть значения
Object
.статические поля не будут проверены. Будут включены поля суперкласса.
таким образом, это должно охватывать ваш случай использования. Очевидный отказ от ответственности: он использует отражение;)
EDIT: это, конечно, предполагает, что поля имеют одинаковые имена, а не типы. В последнем случае можно проверить исходный код и настройка это для их использования.
комбинация двух существующих ответов: общая версия класса-оболочки, предложенная Kayaman (просто список). Использование класса ArrayList::равно как предикат для Пшемек подход Gumula.
я добавил СТРОИТЕЛЬ, чтобы сделать его немного приятнее использовать:
StructureEqual<ClassA, ClassB> struct = StructureEqual.<ClassA, ClassB>builder()
.field(ClassA::getIntA, ClassB::getIntB) // Declare what fields should be checked
.field(ClassA::getStrA, ClassB::getStrB)
.build();
System.out.println(struct.isEqual(myList, mySet));
код:
public class StructureEqual<A, B> {
private List<EqualPoint<A, B>> points;
public StructureEqual(List<EqualPoint<A, B>> points) {
this.points = points;
}
private List<Object> sampleA(A a) {
return points.stream().map(p -> p.getAPoint().apply(a)).collect(Collectors.toList());
}
private List<Object> sampleB(B b) {
return points.stream().map(p -> p.getBPoint().apply(b)).collect(Collectors.toList());
}
public boolean isEqual(Collection<A> as, Collection<B> bs) {
Set<List<Object>> aSamples = as.stream().map(this::sampleA).collect(Collectors.toSet());
Set<List<Object>> bSamples = bs.stream().map(this::sampleB).collect(Collectors.toSet());
return aSamples.equals(bSamples);
}
private static class EqualPoint<PA, PB> {
private final Function<PA, ?> aPoint;
private final Function<PB, ?> bPoint;
public <T> EqualPoint(Function<PA, T> aPoint, Function<PB, T> bPoint) {
this.aPoint = aPoint;
this.bPoint = bPoint;
}
Function<PA, ?> getAPoint() {
return aPoint;
}
Function<PB, ?> getBPoint() {
return bPoint;
}
}
public static <BA, BB> Builder<BA, BB> builder() {
return new Builder<>();
}
public static class Builder<BA, BB> {
private List<EqualPoint<BA, BB>> points = new ArrayList<>();
public <T> Builder<BA, BB> field(Function<BA, T> a, Function<BB, T> b) {
points.add(new EqualPoint<>(a, b));
return this;
}
public StructureEqual<BA, BB> build() {
return new StructureEqual<>(Collections.unmodifiableList(points));
}
}
}
каков самый простой способ сказать, являются ли эти два
Collections
равны ли в терминах указанного подмножества полей?
основываясь на вашем описании, ваши требования равенства:
- коллекции имеют равные размеры.
- для каждого
item1
наcollection1
существуетitem2
наcollection2
такое, чтоitem1.field_x
равнаitem2.field_y
, для нескольких определенныхfield_x
-field_y
пар.
если мы можем предположить, что в любой коллекции нет повторяющихся элементов, то есть, тогда это "самый простой способ" может быть что-то вроде этого:
public boolean areEqual(Collection<ClassA> c1, Collection<ClassB> c2) {
if (c1.size() != c2.size()) {
return false;
}
OUTER:
for (ClassA a : c1) {
for (ClassB b : c2) {
if (a.getIntA() == b.getIntB() && Objects.equals(a.getStringA(), b.getStringB())) {
continue OUTER;
}
}
return false;
}
return true;
}
это простая реализация требований.
Но поскольку он может сравнивать каждый элемент с каждым другим элементом в другой коллекции,
он имеет очень плохую производительность,
O(n^2)
здесь n
- размер коллекции.
это также может не работать, если равные элементы могут появляться несколько раз в коллекция:
List<ClassA> list = new ArrayList<>(Arrays.asList(
new ClassA(1, "A", true),
new ClassA(1, "A", false),
new ClassA(2, "B", true)
));
Set<ClassB> set = new HashSet<>(Arrays.asList(
new ClassB(1, "A", false),
new ClassB(2, "B", false),
new ClassB(2, "B", true)
));
здесь ClassA(1, "A", true)
и ClassA(1, "A", false)
считаются эквивалентными в первом списке, и new ClassB(2, "B", false)
и new ClassB(2, "B", true)
считаются эквивалентными во втором списке.
Вышеприведенный алгоритм найдет эти две коллекции равными, что неверно.
можно обрабатывать случай дубликатов, и в то же время улучшать временную сложность за счет использования дополнительного пространства:
- выполните итерацию над первой коллекцией для построения карты графы
(int, String)
ОК - итерация по второй коллекции при проверке карты подсчетов:
- если количество карт не содержит соответствующего
(int, String)
кортежа,return false
, а это означает, что элемент не имеет пары - если соответствующий кортеж существует, уменьшите его количество
- если количество достигает 0, из кортежа с карты
- если количество карт не содержит соответствующего
- если достигнут конец цикла, то должно означать, что все элементы были сопоставлены (карта должна быть пустой), поэтому вы можете просто
return true
.
реализация:
class FieldExtractingEqual {
public boolean areEqual(Collection<ClassA> c1, Collection<ClassB> c2) {
if (c1.size() != c2.size()) {
return false;
}
Map<Tuple, Integer> counts = new HashMap<>();
for (ClassA a : c1) {
Tuple tuple = new Tuple(a.getIntA(), a.getStringA());
Integer count = counts.get(tuple);
if (count == null) {
count = 0;
}
counts.put(tuple, count + 1);
}
for (ClassB b : c2) {
Tuple tuple = new Tuple(b.getIntB(), b.getStringB());
Integer count = counts.get(tuple);
if (count == null) {
return false;
}
if (count == 1) {
counts.remove(tuple);
} else {
counts.put(tuple, count - 1);
}
}
return true;
}
private static class Tuple {
private final Object[] values;
public Tuple(Object... values) {
this.values = values;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tuple tuple = (Tuple) o;
return Arrays.equals(values, tuple.values);
}
@Override
public int hashCode() {
return Arrays.hashCode(values);
}
}
}
некоторые тесты assertj для проверки реализации:
List<ClassA> myList = new ArrayList<>(Arrays.asList(
new ClassA(1, "A", true),
new ClassA(1, "A", true),
new ClassA(2, "B", true)
));
Set<ClassB> mySet = new HashSet<>(Arrays.asList(
new ClassB(1, "A", false),
new ClassB(1, "A", true),
new ClassB(2, "B", false)
));
FieldExtractingEqual comp = new FieldExtractingEqual();
assertThat(comp.areEqual(myList, mySet)).isTrue();
myList.add(new ClassA(3, "X", true));
mySet.add(new ClassB(3, "Y", true));
assertThat(comp.areEqual(myList, mySet)).isFalse();
как дальнейшее улучшение,
можно сделать реализацию FieldExtractingEqual
generic,
так что это может занять произвольное Collection<A>
и Collection<B>
параметры,
и пары соковыжималки для создания кортежей из A
и B
.
Вот один из способов реализовать это:
interface FieldExtractor<T, V> {
V apply(T arg);
}
class GenericFieldExtractingEqual<T, U> {
private final List<FieldExtractor<T, ?>> extractors1;
private final List<FieldExtractor<U, ?>> extractors2;
private GenericFieldExtractingEqual(List<FieldExtractor<T, ?>> extractors1, List<FieldExtractor<U, ?>> extractors2) {
this.extractors1 = extractors1;
this.extractors2 = extractors2;
}
public boolean areEqual(Collection<T> c1, Collection<U> c2) {
if (c1.size() != c2.size()) {
return false;
}
Map<Tuple, Integer> counts = new HashMap<>();
for (T a : c1) {
Tuple tuple = newTuple1(a);
Integer count = counts.get(tuple);
if (count == null) {
count = 0;
}
counts.put(tuple, count + 1);
}
for (U b : c2) {
Tuple tuple = newTuple2(b);
Integer count = counts.get(tuple);
if (count == null) {
return false;
}
if (count == 1) {
counts.remove(tuple);
} else {
counts.put(tuple, count - 1);
}
}
return true;
}
private Tuple newTuple1(T a) {
return new Tuple(extractors1.stream().map(x -> x.apply(a)).toArray());
}
private Tuple newTuple2(U b) {
return new Tuple(extractors2.stream().map(x -> x.apply(b)).toArray());
}
private static class Tuple {
private final Object[] values;
public Tuple(Object... values) {
this.values = values;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tuple tuple = (Tuple) o;
return Arrays.equals(values, tuple.values);
}
@Override
public int hashCode() {
return Arrays.hashCode(values);
}
}
public static class Builder<T, U> {
List<FieldExtractor<T, ?>> extractors1 = new ArrayList<>();
List<FieldExtractor<U, ?>> extractors2 = new ArrayList<>();
<V> Builder<T, U> addExtractors(FieldExtractor<T, V> extractor1, FieldExtractor<U, V> extractor2) {
extractors1.add(extractor1);
extractors2.add(extractor2);
return this;
}
GenericFieldExtractingEqual<T, U> build() {
return new GenericFieldExtractingEqual<>(new ArrayList<>(extractors1), new ArrayList<>(extractors2));
}
}
}
пример использования и некоторые тесты assertj:
GenericFieldExtractingEqual<ClassA, ClassB> comp2 = new GenericFieldExtractingEqual.Builder<ClassA, ClassB>()
.addExtractors(ClassA::getIntA, ClassB::getIntB)
.addExtractors(ClassA::getStringA, ClassB::getStringB)
.build();
assertThat(comp2.areEqual(myList, mySet)).isTrue();
myList.add(new ClassA(3, "X", true));
mySet.add(new ClassB(3, "Y", true));
assertThat(comp2.areEqual(myList, mySet)).isFalse();
то есть, вы строите GenericFieldExtractingEqual
экземпляр из пар экстракторов, например:
.addExtractors(ClassA::getIntA, ClassB::getIntB)
первый параметр-это объект, который извлекает поле в первый класс, и второй параметр-это объект, который извлекает соответствующее поле во втором классе. Вы добавляете столько пар экстрактора, сколько хотите сравнить для равенства состояние.
хотя я использовал стиль написания Java8 ClassA::getIntA
для компактности,
легко (но долго) конвертировать в FieldExtractor
реализация:
.addExtractors(
new FieldExtractor<ClassA, Integer>() {
@Override
public Integer apply(ClassA arg) {
return arg.getIntA();
}
},
new FieldExtractor<ClassB, Integer>() {
@Override
public Integer apply(ClassB arg) {
return arg.getIntB();
}
}
)
то же самое newTuple*
служебные методы.
вот мой ответ:
public class StackOverFlow {
static class Testy {
int id;
String name;
public Testy(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public int hashCode() {
int hash = 3;
hash = 89 * hash + this.id;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Testy other = (Testy) obj;
if (this.id != other.id || !this.name.equals(other.name)) {
return false;
}
return true;
}
}
static class AnotherTesty {
int id;
String name;
public AnotherTesty(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public int hashCode() {
int hash = 5;
hash = 41 * hash + this.id;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final AnotherTesty other = (AnotherTesty) obj;
if (this.id != other.id || !this.name.equals(other.name)) {
return false;
}
return true;
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
List<Object> list = Arrays.asList(new Testy(5, "test"), new AnotherTesty(5, "test"));
Set<Object> set = new HashSet<>(Arrays.asList(new Testy(5, "test"), new AnotherTesty(5, "test")));
System.out.println(compareCollections(list, set, Testy.class, AnotherTesty.class));
}
private static boolean compareCollections(Collection<?> c1, Collection<?> c2, Class cls, Class cls2) {
List<Object> listOfCls = c1.stream().filter(p -> cls.isInstance(p)).map(o -> cls.cast(o)).collect(Collectors.toList());
List<Object> listOfCls2 = c1.stream().filter(p -> cls2.isInstance(p)).map(o -> cls2.cast(o)).collect(Collectors.toList());
List<Object> list2OfCls = c2.stream().filter(p -> cls.isInstance(p)).map(o -> cls.cast(o)).collect(Collectors.toList());
List<Object> list2OfCls2 = c2.stream().filter(p -> cls2.isInstance(p)).map(o -> cls2.cast(o)).collect(Collectors.toList());
if (listOfCls.size() != list2OfCls.size()||listOfCls2.size() != list2OfCls2.size()) {
return false;
}
boolean clsFlag = true, cls2Flag = true;
for (int i = 0; i < listOfCls.size(); i++) {
if (!listOfCls.get(i).equals(list2OfCls.get(i))) {
clsFlag = false;
break;
}
}
for (int i = 0; i < list2OfCls2.size(); i++) {
if (!listOfCls2.get(i).equals(list2OfCls2.get(i))) {
cls2Flag = false;
break;
}
}
return clsFlag && cls2Flag;
}
}
быстрый прототип:
package stackoverflow;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import org.junit.Test;
public class CompareTwoList {
static class ClassA {
int intA;
String strA;
boolean boolA;
// Constructor
public ClassA(int intA, String strA, boolean boolA) {
this.intA = intA;
this.strA = strA;
this.boolA = boolA;
} // Getters and setters etc. below...
}
static class ClassB {
int intB;
String strB;
boolean boolB;
// Constructor
public ClassB(int intB, String strB, boolean boolB) {
this.intB = intB;
this.strB = strB;
this.boolB = boolB;
} // Getters and setters etc. below...
}
@FunctionalInterface
private interface IncopatibeEqualsOperator<A, B> extends BiFunction<A, B, Boolean> {
}
@Test
public void CompareListOfClassAAndclassBObjects() throws Exception {
List<ClassA> myList = Arrays.asList(
new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<ClassB> mySet = new HashSet<ClassB>(Arrays.asList(
new ClassB(1, "A", false),
new ClassB(2, "B", false)));
// can be extract to separate file
IncopatibeEqualsOperator<ClassA, ClassB> equalsOperatorFlavor1 = (ClassA o1, ClassB o2) -> {
// custom logic here
return o1.intA == o2.intB &&
java.util.Objects.equals(o1.strA, o2.strB);
};
boolean areEquals = areEquals(myList, mySet, equalsOperatorFlavor1);
assertThat(areEquals, is(true));
}
// Add in utility class
private <A, B> boolean areEquals(Collection<A> o1, Collection<B> o2, IncopatibeEqualsOperator<A, B> comparator) {
if (o1.size() == o2.size()) { // if size different; they are not equals
for (A obj1 : o1) {
boolean found = false; // search item of o1 into o2; algorithm
// can be improve
for (B obj2 : o2) {
if (comparator.apply(obj1, obj2)) { // call custom code of
// comparision
found = true;
break;
}
}
if (!found) {// if current element of o1 is not equals with any
// one return false
return false;
}
}
return true;// all are matched
}
return false;
}
}
убедитесь, что классы A и B имеют методы toString ().
ClassA
public class ClassA {
private int intA;
private String strA;
private boolean boolA;
// Constructor
public ClassA (int intA, String strA, boolean boolA) {
this.intA = intA; this.strA = strA; this.boolA = boolA;
} //
@Override
public String toString()
{
return intA + " " + strA + " " + boolA;
}
}
ClassB
public class ClassB {
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB (int intB, String strB, boolean boolB) {
this.intB = intB; this.strB = strB; this.boolB = boolB;
} // Gett
@Override
public String toString()
{
return intB + " " + strB + " " + boolB;
}
}
Main / Test
public class JavaApplication11 {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<Object> mySet = new HashSet<Object>(
Arrays.asList(new ClassB(1, "A", false),
new ClassB(2, "B", false)));
System.out.println("is equal: " + isEqual(myList, mySet));
}
static boolean isEqual(Object list, Object set)
{
System.out.println(list.toString());
System.out.println(set.toString());
String tempStringA = list.toString();
tempStringA = tempStringA.replaceAll("true", "");
tempStringA = tempStringA.replaceAll("false", "");
String tempStringB = set.toString();
tempStringB = tempStringB.replaceAll("true", "");
tempStringB = tempStringB.replaceAll("false", "");
return tempStringA.equals(tempStringB);
}
}
вы должны взять основную идею EqualsBuilder но изменено для ваших нужд: создайте какой-то список с членами (или лучшими геттерами) для сравнения, например. хеш-карта. Теперь повторите эту карту, найдите функции в классе A с ключевой записью карты. Затем выполните поиск функции класса B с записью Значения карты. Вызывать (вызвать) и сравните результат.
HashMap<String,String> mymap=new HashMap<>();
mymap.put("intA","intB");
mymap.put("boolA","boolB");
for(Map.Entry<String,String> e:mymap.entrySet()) {
// names not ok, maybe take a bean helper class
Method m1=a.getClass().getMethod("get"+e.getKey()); // or look for fields if you dont have getters
Method m2=b.getClass().getMethod("get"+e.getValue());
Object r1=m1.invoke(a);
Object r2=m2.invoke(b);
if (!r1.equals(r2))
return false;
}
извините за отсутствие реального кода. Проверки Null должны быть добавлены!
public class Compare {
public static void main(String[] args) {
// TODO Auto-generated method stub
Compare compare= new Compare();
List<ClassA> myList = Arrays.asList(new ClassA(1, "A", false), new ClassA(2, "B", false));
Set<ClassB> mySet = new HashSet<ClassB>(Arrays.asList(new ClassB(1, "A", false), new ClassB(2, "B", false)));
System.out.println( compare.areEqual(myList,mySet));
}
public boolean areEqual(Collection<ClassA> colA,Collection<ClassB> colB){
boolean equal =false;
if(colA.size()!=colB.size()){
return equal;
}
Set<Integer> setA=new HashSet<Integer>();
Set<Integer> setB= new HashSet<Integer>();
for(ClassA obj : colA){
setA.add(obj.hashCode());
}
for(ClassB obj : colB){
setB.add(obj.hashCode());
}
if(setA.equals(setB)){
equal=true;
}
return equal;
}
}
class ClassA {
private int intA;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + intA;
result = prime * result + ((strA == null) ? 0 : strA.hashCode());
return result;
}
private String strA;
private boolean boolA;
// Constructor
public ClassA(int intA, String strA, boolean boolA) {
this.intA = intA;
this.strA = strA;
this.boolA = boolA;
} // Getters and setters etc. below...
}
class ClassB {
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + intB;
result = prime * result + ((strB == null) ? 0 : strB.hashCode());
return result;
}
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB(int intB, String strB, boolean boolB) {
this.intB = intB;
this.strB = strB;
this.boolB = boolB;
} // Getters and setters etc. below...
}
Ну, я переопределяю метод хэш-кода обоих классов для создания хэш-кода на основе int и str и метод для создания наборов Интергеров, целое число является хэш-кодом каждого класса, если вы не хотите, чтобы даже хэш-код был переопределен, дайте мне знать, обновится для этого также
может это поможет..
class ClassA {
private int intA;
private String strA;
private boolean boolA;
// Constructor
public ClassA(int intA, String strA, boolean boolA) {
this.intA = intA;
this.strA = strA;
this.boolA = boolA;
} // Getters and setters etc. below...
@Override
public boolean equals(Object obj) {
if (obj instanceof ClassA) {
ClassA obj2 = (ClassA) obj;
return (this.intA == obj2.intA && this.strA.equals(obj2.strA) && this.boolA == obj2.boolA);
} else {
ClassB obj2 = (ClassB) obj;
return (this.intA == obj2.intB && this.strA.equals(obj2.strB) && this.boolA == obj2.boolB);
}
}
@Override
public int hashCode() {
int hash = 3;
hash = 71 * hash + this.intA;
hash = 71 * hash + Objects.hashCode(this.strA);
hash = 71 * hash + (this.boolA ? 1 : 0);
return hash;
}
}
class ClassB {
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB(int intB, String strB, boolean boolB) {
this.intB = intB;
this.strB = strB;
this.boolB = boolB;
} // Getters and setters etc. below...
@Override
public boolean equals(Object obj) {
if (obj instanceof ClassB) {
ClassB obj2 = (ClassB) obj;
return (this.intB == obj2.intB && this.strB.equals(obj2.strB) && this.boolB == obj2.boolB);
} else {
ClassA obj2 = (ClassA) obj;
return (this.intB == obj2.intA && this.strB.equals(obj2.strA) && this.boolB == obj2.boolA);
}
}
@Override
public int hashCode() {
int hash = 5;
hash = 79 * hash + this.intB;
hash = 79 * hash + Objects.hashCode(this.strB);
hash = 79 * hash + (this.boolB ? 1 : 0);
return hash;
}
}
public void test() {
List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(1, "A", true));
System.out.println(myList.get(0).equals(myList.get(1)));
}
в то время как для двух отдельных элементов эквивалентное сравнение однозначно определено, для коллекций возможны несколько вариантов эквивалентного сравнения. Один из аспектов заключается в том, следует ли рассматривать упорядочение элементов. Далее, когда порядок не является значительным, то мощность эквивалентных элементов (количество совпадений) может быть или не быть значительной.
поэтому предложение использования EquivalenceComparisonBuilder
, на котором вместе с двумя коллекциями и EquivalenceComparator
также ComparisonType
настраивается - ComparisonType.ORDERING
для строгой упорядоченности, ComparisonType.DUPLICATES
для строгого подсчета матчей и ComparisonType.SIMPLE
для сравнения свободной эквивалентности, где достаточно, чтобы для каждого элемента в одной коллекции был хотя бы один эквивалентный элемент в другой коллекции.
обратите внимание, что выполнение EquivalenceComparator
необходимо считать null
аргументы, если коллекции могут содержать null
элементы.
package equivalence;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
public class Equivalence {
public static interface EquivalenceComparison<S, T> {
boolean equivalent();
}
public static interface EquivalenceComparator<S, T> {
boolean equivalent(S s, T t);
}
static public class EquivalenceComparisonBuilder<S, T> {
enum ComparisonType {
ORDERING, DUPLICATES, SIMPLE
};
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
private ComparisonType comparisonType;
public EquivalenceComparisonBuilder<S, T> setCollections(Collection<S> ss, Collection<T> ts) {
this.ss = ss;
this.ts = ts;
return this;
}
public EquivalenceComparisonBuilder<S, T> setEquivalenceComparator(EquivalenceComparator<S, T> ec) {
this.ec = ec;
return this;
}
public EquivalenceComparisonBuilder<S, T> setComparisonType(ComparisonType comparisonType) {
this.comparisonType = comparisonType;
return this;
}
public EquivalenceComparison<S, T> comparison() {
if (comparisonType == null || ss == null || ts == null) {
throw new NullPointerException();
}
switch (comparisonType) {
case ORDERING:
return new OrderingComparison<S, T>(ss, ts, ec);
case DUPLICATES:
return new DuplicatesComparison<S, T>(ss, ts, ec);
case SIMPLE:
return new SimpleComparison<S, T>(ss, ts, ec);
default:
throw new IllegalArgumentException("Unknown comparison type");
}
}
}
private static <S, T> EquivalenceComparator<T, S> mirrored(EquivalenceComparator<S, T> ec) {
return new EquivalenceComparator<T, S>() {
@Override
public boolean equivalent(T t, S s) {
return ec.equivalent(s, t);
}
};
}
private static class EquivalencePredicate<S, T> implements Predicate<T> {
private S s;
private EquivalenceComparator<S, T> equivalenceComparator;
public EquivalencePredicate(S s, EquivalenceComparator<S, T> equivalenceComparator) {
this.s = s;
this.equivalenceComparator = equivalenceComparator;
}
@Override
public boolean evaluate(T t) {
return equivalenceComparator.equivalent(s, t);
}
}
static private class OrderingComparison<S, T> implements EquivalenceComparison<S, T> {
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
public OrderingComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
this.ss = ss;
this.ts = ts;
this.ec = ec;
}
@Override
public boolean equivalent() {
if (ss.size() != ts.size()) {
return false;
}
List<S> ssl = new ArrayList<S>(ss);
List<T> tsl = new ArrayList<T>(ts);
for (int i = 0; i < ssl.size(); i++) {
S s = ssl.get(i);
T t = tsl.get(i);
if (!ec.equivalent(s, t)) {
return false;
}
}
return true;
}
}
static private class DuplicatesComparison<S, T> implements EquivalenceComparison<S, T> {
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
public DuplicatesComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
this.ss = ss;
this.ts = ts;
this.ec = ec;
}
@Override
public boolean equivalent() {
if (ss.size() != ts.size()) {
return false;
}
for (S s : ss) {
Collection<T> matchingTs = CollectionUtils.select(ts, new EquivalencePredicate(s, ec));
if (matchingTs.size() == 0) {
return false;
}
T t = matchingTs.iterator().next();
Collection<S> matchingSs = CollectionUtils.select(ss, new EquivalencePredicate(t, mirrored(ec)));
if (matchingTs.size() != matchingSs.size()) {
return false;
}
}
return true;
}
}
static private class SimpleComparison<S, T> implements EquivalenceComparison<S, T> {
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
public SimpleComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
this.ss = ss;
this.ts = ts;
this.ec = ec;
}
@Override
public boolean equivalent() {
for (S s : ss) {
if (!CollectionUtils.exists(ts, new EquivalencePredicate(s, ec))) {
return false;
}
}
for(T t :ts) {
if (!CollectionUtils.exists(ss, new EquivalencePredicate(t, mirrored(ec)))) {
return false;
}
}
return true;
}
}
}
вот несколько тестов:
package equivalence;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.junit.Assert;
import org.junit.Test;
import equivalence.Equivalence.EquivalenceComparator;
import equivalence.Equivalence.EquivalenceComparisonBuilder;
import equivalence.Equivalence.EquivalenceComparisonBuilder.ComparisonType;
public class EquivalenceExample {
static class A {
private int ia;
private String is;
private long a;
public A(int ia, String is, long a) {
this.ia = ia;
this.is = is;
this.a = a;
}
public int getIa() {
return ia;
}
public String getIs() {
return is;
}
public long getA() {
return a;
}
}
static class B {
private int ib;
private String is;
private long b;
public B(int ib, String is, long b) {
this.ib = ib;
this.is = is;
this.b = b;
}
public int getIb() {
return ib;
}
public String getIs() {
return is;
}
public long getB() {
return b;
}
}
static class ABEquivalenceComparator implements EquivalenceComparator<A, B> {
static public ABEquivalenceComparator INSTANCE = new ABEquivalenceComparator();
@Override
public boolean equivalent(A a, B b) {
return new EqualsBuilder().append(a.getIa(), b.getIb()).append(a.getIs(), b.getIs()).isEquals();
}
}
@Test
public void thatOrderingEquivalenceMatchesEquivalentElementsWhenInSameOrder() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
.comparison().equivalent());
}
@Test
public void thatOrderingEquivalenceDoesNotMatchEquivalentElementsWhenNotSameOrdering() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
.comparison().equivalent());
}
@Test
public void thatOrderingEquivalenceDoesNotMatchNonEquivalentElements() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(1, "1", 99l), new B(1, "1", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
.comparison().equivalent());
}
@Test
public void thatDuplicatesEquivalenceMatchesEquivalentElementsRegardlessOrder() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.DUPLICATES)
.comparison().equivalent());
}
@Test
public void thatDuplicatesEquivalenceDoesNotMatchesWhenElementsCardinlityDoNotMatch() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l), new A(1, "1", 99l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(
Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.DUPLICATES)
.comparison().equivalent());
}
@Test
public void thatSimpleEquivalenceMatchesRegardlessEquivalentElementCardinality() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l), new A(1, "1", 99l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(
Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
.comparison().equivalent());
}
@Test
public void thatSimpleEquivalenceMatchesRegardlessElementsCount() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(
Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
.comparison().equivalent());
}
@Test
public void thatSimpleEquivalenceDoesMatchesWhenElementsDoNotMatch() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(3, "3", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
.comparison().equivalent());
}
}