Как сравнить две коллекции для "эквивалентности" на основе полей из разных классов 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 "эквивалентными" (*) в терминах заданного подмножества полей?

(*) Слово "эквивалент" используется, а не "равно", поскольку оно является контекстуальным - то есть такая "эквивалентность" может быть определена по-разному в другом контексте.

Работающий пример сверху: Предположим, мы укажем, что intA и strA должны совпадать с intB и strB соответственно (но значения boolA/boolB можно игнорировать). Это сделало бы упомянутые выше два объекта коллекции эквивалентными, но если бы элемент был добавлен или удален из одной из коллекций, то они больше не были бы.

Предпочтительное решение: Используемый метод должен быть общим для любого типа Collection. В идеале Java 7 как-то ограничивается этим (но Java 8 может представлять интерес для других). С удовольствием используем Guava или Apache Commons, но предпочитаем не использовать более неясные внешние библиотеки.

Ответ 1

Здесь версия Java 8, использующая функции lambdas и более высокого порядка. Вероятно, это можно преобразовать в Java 7, используя анонимные внутренние классы вместо lambdas. (Я считаю, что у большинства IDE есть операция рефакторинга, которая делает это.) Я оставлю это как упражнение для заинтересованных читателей.

Здесь есть две различные проблемы:

  • Учитывая два объекта разных типов, оцените их, исследуя соответствующие поля каждого из них. Это отличается от операций "равно" и "сравнивать", которые уже определены API-интерфейсами библиотеки JDK, поэтому вместо этого я использую термин "эквивалент".

  • Учитывая две коллекции, содержащие элементы этих типов, определите, являются ли они "равными" для некоторого определения этого термина. Это на самом деле довольно тонко; см. обсуждение ниже.

1. Эквивалентность

Для двух объектов типов T и U мы хотим определить, являются ли они эквивалентными. Результат - логическое. Это может быть представлено функцией типа BiPredicate<T,U>. Но мы не можем напрямую исследовать объекты напрямую; вместо этого нам нужно извлечь соответствующие поля из каждого объекта и оценить результаты извлечения друг на друга. Если поле, извлеченное из T, имеет тип TR, а поле, выделенное из U, имеет тип UR, то экстракторы представлены типами функций

Function<T, TR>
Function<U, UR>

Теперь мы извлекли результаты типа TR и UR. Мы могли бы просто называть equals() на них, но это излишне ограничительное. Вместо этого мы можем предоставить другую функцию эквивалентности, которая будет вызываться для оценки этих двух результатов друг против друга. Что a 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);
}

Это позволяет нам строить функции эквивалентности, основанные на одном поле. Что делать, если мы хотим оценить эквивалентность, основанную на нескольких полях? Мы можем комбинировать произвольное число BiPredicates, объединяя метод 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. Коллекция "Равенство"

Вторая часть - это оценка того, являются ли две коллекции "равными" в некотором смысле. Проблема в том, что в Framework Collections понятие равенства для определено таким образом, что список может быть равен только другому списку, а набор может быть равен только другому набору. Из этого следует, что коллекция какого-либо другого типа никогда не может быть равна ни списку, ни набору. См. Спецификацию Collection.equals() для некоторого обсуждения этой точки.

Это явно противоречит желанию OP. Как было предложено OP, мы действительно не хотим "равенства", но нам нужно другое свойство, для которого нам нужно дать определение. На основе примеров OP и некоторых предложений в других ответах Przemek Gumula и janos, похоже, что мы хотите, чтобы элементы из двух коллекций каким-то образом соответствовали друг другу. Я назову это bijectionкоторый может быть не математически точным, но он кажется достаточно близким. Кроме того, соответствие между каждой парой элементов должно быть эквивалентом, определенным выше.

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

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

(Это по существу то же самое, что указано Przemek 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 делают это довольно простым. Это очень похоже на алгоритм, использованный в ответе janos, хотя я выделил функцию эквивалентности в параметр метода. Увы, поскольку моя функция эквивалентности может иметь вложенные функции эквивалентности, это означает, что я не могу исследовать карту (которая определяется равенством). Вместо этого я должен искать ключи карты, что означает, что алгоритм 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 равен false. Но если a соответствует 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.

Ответ 2

Еще одно возможное решение - написать простой метод сравнения с предикатом (так что вы можете явно указать условие для того, чтобы два класса были похожи на ваши термины). Я создал это в 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, реализовав простой код BiPredicate, allMatch и anyMatch (один для цикла для каждого из них должен быть достаточным)

Изменить: код 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);
}

Здесь приведен пример использования .

Ответ 3

Нет простого способа.

Наиболее общим, который будет работать с обычными коллекциями Java, будет создание класса-оболочки, который будет принимать как ClassA, так и ClassB в качестве входных данных, а затем переопределить equals/hashcode, как определено вами.

В некоторых случаях вы можете злоупотреблять Comparator, но это ограничит вас TreeMap/TreeSet. Вы также можете использовать метод equals() для работы, чтобы classA.equals(classB); возвращал true, но это может вызвать сложные ошибки, если вы не будете осторожны. Это также может привести к интересным ситуациям, когда a.equals(b) и b.equals(c), но !a.equals(c).

В некоторой библиотеке (Guava?) также был установлен стиль стиля Comparator для тестирования равенства, но это будет работать только с коллекциями библиотек.

Ответ 4

Apache Commons Lang имеет EqualsBuilder#reflectionEquals(Object, Object):

Этот метод использует отражение, чтобы определить, равны ли два Object.

Он использует AccessibleObject.setAccessible для доступа к приватным поля. Это означает, что он выдает исключение безопасности, если выполняется под менеджером безопасности, если разрешения не настроены правильно. Это также не так эффективно, как тестирование явно. Непримитивный поля сравниваются с помощью equals().

Участники переходного процесса не будут тестироваться, поскольку они, вероятно, получены поля, а не часть значения Object.

Статические поля не будут проверены. Поля суперкласса будут включены.

Таким образом, это должно охватывать ваш прецедент. Очевидный отказ от ответственности: он использует отражение;)

EDIT: Это, конечно, предполагает, что поля имеют одинаковые имена, а не типы. В последнем случае можно проверить исходный код и настроить его в своем случае.

Ответ 5

Комбинация двух существующих ответов: предложена общая версия класса-оболочки Kayaman (Just List). Использование ArrayList:: равно предикату для подхода Прземека Гумулы.

Я добавил Builder, чтобы сделать его более удобным:

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

}

Ответ 6

Какой самый простой способ сказать, равны ли два 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) tuples
  • Итерации по второй коллекции при проверке карты отсчетов:
    • Если количество карт не содержит соответствующий набор (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*.

Здесь запускается версия RexTester.

Ответ 7

Вот мой ответ:

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

}

Ответ 8

быстрый прототип:

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

Ответ 9

Убедитесь, что для классов 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;
    }
}

Главная /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);
    }

}

Ответ 10

Вы должны взять базовую идею EqualsBuilder, но изменить ее в соответствии со своими потребностями: создать какой-то список с членами (или лучшие геттеры) для сравнения, например. HashMap. Теперь итерируйте эту карту, ищите функции в классе 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;
}

Извините за отсутствие реального кода. Нулевые проверки должны быть добавлены!

Ответ 11

        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 и метода для создания наборов Intergers, Integer - хэш-код каждого класса, если вы не хотите, чтобы даже хэш-код был overridden, дайте мне знать, обновит для этого также

Ответ 12

Пусть это поможет..

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

    }

Ответ 13

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

Поэтому предложение с использованием EquivalenceComparisonBuilder, в котором вместе с двумя коллекциями и EquivalenceComparator также настроено ComparisonType - ComparisonType.ORDERING для строгой упорядочивания, ComparisonType.DUPLICATES для строгих совпадений count и 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());
    }

    }