Научитесь сравнивать два списка в Java так, чтобы оба списка содержали абсолютно одинаковые элементы в любом порядке, а вхождения каждого элемента списка были одинаковыми в обоих списках.
1. Использование CollectionUtils.isEqualCollection() из Common Collections4
Чтобы использовать этот API, включите последнюю версию commons-collections4 из репозитория Maven.
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.4</version></dependency>
API isEqualCollection() возвращает значение true, если оба списка содержат абсолютно одинаковые элементы с абсолютно одинаковой мощностью(т. е. элемент присутствует одинаковое количество раз в обоих списках).
List<String> list = Arrays.asList("a", "b", "c");List<String> equalList = Arrays.asList("b", "c", "a");List<String> unequalList = Arrays.asList("d", "c", "a");assertTrue(CollectionUtils.isEqualCollection(list, equalList));assertFalse(CollectionUtils.isEqualCollection(list, unequalList));
Мы можем убедиться, что при сравнении списков учитывается также мощность.
assertFalse(CollectionUtils.isEqualCollection(Arrays.asList("a", "a", "b"),Arrays.asList("a", "b", "b")));assertTrue(CollectionUtils.isEqualCollection(Arrays.asList("a", "b", "b"),Arrays.asList("a", "b", "b")));
По умолчанию элементы списка сравниваются с помощью метода equals(). Иногда мы получаем списки из разных источников, и элементы списка могут не содержать все поля, необходимые для правил равенства по умолчанию. Мы также можем предоставить пользовательскую логику равенства, предоставив пользовательскую реализацию интерфейса Equator.
В следующем примере мы сравниваем экземпляры String, игнорируя ВЕРХНИЙ или нижний регистр. Обратите внимание, что мощности проверяются с использованием экземпляра Map, поэтому необходимо, чтобы одни и те же элементы были преобразованы в одни и те же ключи. В этом случае все ключи будут преобразованы в строки ВЕРХНЕГО РЕГИСТРА.
class StringCaseEquator implements Equator<String> {public boolean equate(String s1, String s2) {return s1.equalsIgnoreCase(s2);}@Overridepublic int hash(String s) {return s.toUpperCase().hashCode();}}assertTrue(CollectionUtils.isEqualCollection(Arrays.asList("a", "b"),Arrays.asList("A", "B"),new StringCaseEquator()));
2. Использование Matchers.containsInAnyOrder() от Hamcrest.
Чтобы использовать этот API, включите последнюю версию hamcrest-all из репозитория Maven.
<dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-all</artifactId><version>1.3</version></dependency>
API containsInAnyOrder() создает независимый от порядка сопоставитель для Iterables, который сопоставляет элементы двух списков, игнорируя порядок элементов в списке. Для положительного совпадения оба списка должны быть одинакового размера, поэтому нам не нужно явно сравнивать размеры.
List<String> list = Arrays.asList("a", "b", "c");List<String> equalList = Arrays.asList("b", "c", "a");assertThat(list, Matchers.containsInAnyOrder(equalList.toArray()));
Это решение не поддерживает пользовательскую логику равенства для элементов списка.
3. Использование API AssertJ containsExactlyInAnyOrderElementsOf()
Начните с добавления последней версии assertj-core из репозитория Maven.
<dependency><groupId>org.assertj</groupId><artifactId>assertj-core</artifactId><version>3.16.1</version></dependency>
Функция containsExactlyInAnyOrderElementsOf() проверяет, что первый список содержит все элементы второго списка, и каждый элемент встречается одинаковое количество раз в любом порядке.
List<String> list = Arrays.asList("a", "b", "c");List<String> equalList = Arrays.asList("b", "c", "a");org.assertj.core.api.Assertions.assertThat(list).containsExactlyInAnyOrderElementsOf(equalList);
Любое дополнительное вхождение любого элемента в список приведет к невыполнению утверждения.
4. Обычный Java или JUnit
Если мы не хотим использовать никакую стороннюю библиотеку, мы можем разработать наше решение с помощью метода List.remove(). В следующем коде мы перебираем первый список и удаляем первое вхождение текущего элемента из второго списка.
После завершения итерации второй список должен быть пустым. Обратите внимание, что этот подход использует API remove(), доступный в классе ArrayList. Поэтому, если вы используете неизменяемый список, рассмотрите возможность обернуть его в экземпляры ArrayList.
static boolean compareListsIgnoringOrder(ArrayList list1, ArrayList list2) {if(list1 == null || list2 == null) return false;if(list1.size() != list2.size()) return false;for(Object o : list1) {list2.remove(o);}if(list2.size() != 0) return false;return true;}List<String> firstList = Arrays.asList("a", "b", "c", "c");List<String> secondList = Arrays.asList("b", "c", "a", "c");assertTrue(compareListsIgnoringOrder(new ArrayList(firstList), new ArrayList<>(secondList)));
5. Заключение
Сравнение двух списков Java с игнорированием порядка является обычным требованием во время модульных тестов, когда оба списка поступают из разных источников, и мы должны проверить, что оба списка имеют одинаковые элементы независимо от их порядка в списке. В этом руководстве показаны примеры с использованием Apache Common Collections4, Hamcrest, AssertJ и простых API Java для использования в операторах утверждений JUnit.