How to Check if Two Lists are Equal
There are three cases when we are talking about this topic.
Case 1: two lists are strictly equal, they have the same size, the elements in the lists have same order, and all corresponding pairs of elements in the two lists are equal;
Case 2: two lists containing the same elements, they have the same size, and there are duplicate elements within the lists. However, the number of counts each element appears in the two lists must be the same.
Case 3: two lists containing the same elements, regardless of element orders or whether the elements have the same duplication counts.
Let’s discuss one by one.
Case 1: Two lists are Strictly Equal
If we care about element order, we can just use equals()
method provided in the Collection
class.
List<String> list1 = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> list2 = new ArrayList<>(Arrays.asList("a", "b", "c"));
log.info("{}", list1.equals(list2)); // returns true
List<String> list3 = new ArrayList<>(Arrays.asList("a", "b", "a"));
List<String> list4 = new ArrayList<>(Arrays.asList("a", "a", "b"));
log.info("{}", list3.equals(list4)); // returns false because of dismatch orders
The equals()
method also works on custom objects. Suppose we have an UserIdCard
class with three fields: id
, name
, age
. We see two UserIdCard
objects are the same if they have the same id
value, regardless of the other two fields.
We can reduce lots of work to create UserIdCard
class with help of Lombok. By using @EqualsAndHashCode(onlyExplicitlyIncluded = true)
on class and @EqualsAndHashCode.Include
annotation on id
fields, id
filed is included as the only checking field in the equals()
and hashcode()
method implementations.
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class UserIdCard {
@EqualsAndHashCode.Include
private Integer id;
private String name;
private Integer age;
}
As the below codes shown, list5 is equals to list6 because the id
of corresponding user in both lists are equal.
elements in list7 has different order with elements in list8, so list7.equals(list8)
returns false in the end.
List<UserIdCard> list5 = new ArrayList<>(Arrays.asList(
new UserIdCard(1, "tom", 30),
new UserIdCard(2, "jack", 25),
new UserIdCard(3, "joey", 30)
));
List<UserIdCard> list6 = new ArrayList<>(Arrays.asList(
new UserIdCard(1, "tom", 20),
new UserIdCard(2, "jack", 20),
new UserIdCard(3, "joey", 20)
));
log.info("{}", list5.equals(list6)); // returns true
List<UserIdCard> list7 = new ArrayList<>(Arrays.asList(
new UserIdCard(1, "tom", 30),
new UserIdCard(2, "jack", 25),
new UserIdCard(3, "joey", 30)
));
List<UserIdCard> list8 = new ArrayList<>(Arrays.asList(
new UserIdCard(1, "tom", 30),
new UserIdCard(2, "jack", 25),
new UserIdCard(4, "joey", 30)
));
log.info("{}", list7.equals(list8)); // returns false
An alternative to Collection.equals()
is Objects.equals()
from java.util package. Below is the implementation of Objects.equals()
:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
From the souce code, we can know that Objects.equals()
internally calls equals()
method of object. The advantage of using this method is that there’s no need to explicitly handle nulls because Objects.equals()
helps doing this.
When we reuse the declaration of list1 to list8 and check using Objects.equals()
, we get the same result as when we use Collection.equals()
.
log.info("list1 is equals to list2: {}", Objects.equals(list1, list2)); // returns true
log.info("list3 is equals to list4: {}", Objects.equals(list3, list4)); // returns false
log.info("list5 is equals to list6: {}", Objects.equals(list5, list6)); // returns true
log.info("list7 is equals to list8: {}", Objects.equals(list7, list8)); // returns false
Case 2: Two Lists Containing the Same Elements with the Same Duplicate Counts
Let’s understand this case by several examples directly.
list1
with elements (“a”, “b”, “b”) is equal tolist2
with elements (“b”, “b”, “a”) because they both contain “a” and “b”, and there are one “a” and two “b” in both list.list3
with elements (“a”, “a”) is not equal tolist4
with elements (“a”, “a”, “a”) because even though both lists contain “a”, they have different sizes, andlist3
has two “a” butlist4
has three “a”.
CollectionUtils.isEqualCollection()
method from apache.commons.collections4 is the best fit to solve this case.
List<String> list1 = new ArrayList<>(Arrays.asList("a", "b", "b"));
List<String> list2 = new ArrayList<>(Arrays.asList("b", "b", "a"));
log.info("{}", CollectionUtils.isEqualCollection(list1, list2)); // returns true
List<String> list3 = new ArrayList<>(Arrays.asList("a", "a"));
List<String> list4 = new ArrayList<>(Arrays.asList("a", "a", "a"));
log.info("{}", CollectionUtils.isEqualCollection(list3, list4)); // returns false
If we are not allowed to have apache.commons.collections4 in our project, we have to write our own version of isEqualCollection()
, let’s get idea from the source code of isEqualCollection()
:
public static boolean isEqualCollection(final Collection<?> a, final Collection<?> b) {
if(a.size() != b.size()) {
return false;
}
final CardinalityHelper<Object> helper = new CardinalityHelper<>(a, b);
if(helper.cardinalityA.size() != helper.cardinalityB.size()) {
return false;
}
for( final Object obj : helper.cardinalityA.keySet()) {
if(helper.freqA(obj) != helper.freqB(obj)) {
return false;
}
}
return true;
}
The key idea of isEqualCollection()
is to convert both lists to maps. The key of map is the elements in the lists, and the value of the map is the elements occurrences in the lists. By doing this, we can check key set of maps and see if two lists containing the same value, and we can know if element occurrences are equals in the two lists.
Our own version of isEqualCollection()
could be like this:
private static boolean isEqualsCollection(List<String> a, List<String> b) {
if(a.size() != b.size()) {
return false;
}
Map<String, Integer> aOccuranceMap = getOccurrenceMap(a);
Map<String, Integer> bOccuranceMap = getOccurrenceMap(b);
// check if two lists containing the same elements
if(aOccuranceMap.size() != bOccuranceMap.size()) {
return false;
}
// check if element occurrences are equals in the two lists
for( final String element : aOccuranceMap.keySet()) {
if(!aOccuranceMap.get(element).equals(bOccuranceMap.get(element))) {
return false;
}
}
return true;
}
public static Map<String, Integer> getOccurrenceMap(final List<String> a) {
final Map<String, Integer> count = new HashMap<>();
for (final String element : a) {
count.merge(element, 1, Integer::sum);
}
return count;
}
Case 3: Two Lists Containing the Same Elements, Regardless of Orders or Dupliates
In most cases, we just want to know if two lists containing the same elements, it is acceptable that elements have different duplicate counts in the two lists. We can simply use List.containsAll()
method to achieve this goal.
List<String> list1 = new ArrayList<>(Arrays.asList("a", "b", "b"));
List<String> list2 = new ArrayList<>(Arrays.asList("b", "b"));
log.info("{}", list1.containsAll(list2) && list2.containsAll(list1)); // returns false
List<String> list3 = new ArrayList<>(Arrays.asList("a", "a", "a"));
List<String> list4 = new ArrayList<>(Arrays.asList("a"));
log.info("{}", list3.containsAll(list4) && list4.containsAll(list3)); // returns true
We may also convert list to set and use Set.containsAll()
instead when there are lots of duplicates in the lists.
Set<String> set1 = new HashSet<>(list1);
Set<String> set2 = new HashSet<>(list2);
log.info("{}", set1.containsAll(set2) && set2.containsAll(set1)); // returns false