Correct Way to Assert Two Equal Lists Ignoring Order

Learn to compare two lists in Java such that both lists contain exactly the same items in any order, and the occurrences of each list item must be equal in both lists.

1. Using Common Collections4’s CollectionUtils.isEqualCollection()

To use this API, include the latest version of commons-collections4 from the Maven repository.

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

The isEqualCollection() API returns true if both Lists contain exactly the same elements with exactly the same cardinalities (i.e. an element is present exactly the same number of times in both lists).

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

We can verify that cardinality is also taken into account while comparing the lists.

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

By default, List items are compared using the equals() method. Sometimes, we receive lists from different sources, and the list items may not contain all the fields needed for default equality rules. We can also supply the custom equality logic by supplying the custom implementation of Equator interface.

In the following example, we are comparing the String instances ignoring the UPPERCASE or lowercase. Note that cardinalities are checked using the Map instance so it is necessary that the same items must be transformed into the same keys. In this case, all keys will be converted to UPPERCASE strings.

class StringCaseEquator implements Equator<String> {

  public boolean equate(String s1, String s2) {
    return s1.equalsIgnoreCase(s2);
  }

  @Override
  public int hash(String s) {
    return s.toUpperCase().hashCode();
  }
}

assertTrue(CollectionUtils.isEqualCollection(
    Arrays.asList("a", "b"),
    Arrays.asList("A", "B"),
    new StringCaseEquator()));

2. Using Hamcrest’s Matchers.containsInAnyOrder()

To use this API, include the latest version of hamcrest-all from the Maven repository.

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
</dependency>

The containsInAnyOrder() API creates an order-agnostic matcher for Iterables, which matches the elements of two lists, ignoring the order of elements in the list. For a positive match, both lists must be of the same size, so we don’t need to compare the sizes explicitly.

List<String> list = Arrays.asList("a", "b", "c");
List<String> equalList = Arrays.asList("b", "c", "a");

assertThat(list, Matchers.containsInAnyOrder(equalList.toArray()));

This solution does not support the custom equality logic for list items.

3. Using AssertJ’s containsExactlyInAnyOrderElementsOf() API

Start with adding the latest version of assertj-core from the Maven repository.

<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.16.1</version>
</dependency>

The containsExactlyInAnyOrderElementsOf() verifies that the first list contains all the elements of the second list, and each element occurred exactly the same number of times, in any order.

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

Any extra occurrence of any item in the list will fail the assertion.

4. Plain Java or JUnit

If we do not want to use any 3rd-party library, we can devise our solution using the List.remove() method. In the following code, we are iterating over the first list and removing the first occurrence of the current item from the second list.

After the iteration is complete, the second list must be empty. Note that this approach uses the remove() API that is available on ArrayList class. So if you are using an immutable list, consider wrapping them inside ArrayList instances.

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. Conclusion

Comparing two Java lists with order ignored is a common requirement during unit tests where both lists come from different sources, and we must check that both lists have the same elements irrespective of their order in the list. This tutorial shows examples using Apache Common Collections4, Hamcrest, AssertJ and plain Java APIs for use in JUnit assert statements.

Happy Learning !!

Sourcecode on Github

Comments

Subscribe
Notify of
guest
2 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode