Comparing Collections in Java
Learn when to use List
, Set
, Bag
or Map
if you don’t have a Bag
.
Test Infected and Happy!
I love writing executable code examples in unit tests. Using JUnit Assertions
is so much more interesting and sometimes more challenging than just writing a main()
method with some System.out.println()
methods to experiment. Writing tests gives me confidence that my code works today, and confidence tomorrow that the code I wrote still works as written. If it doesn’t work, the test will fail and tell me.
Two of the most common JUnit 5 Assertions
that I use are assertEquals
and assertNotEquals
. When these methods fail, they provide useful messages explaining what the difference is between the two objects being compared based on their toString
result.
These two Assertions
are very useful when it comes to comparing two Collections in Java. Developers who use the Java Collection Framework have three basic Collection types that they can use to compare for equality. These types are java.util.List
, java.util.Set
, and java.util.Map
. In Eclipse Collections, there is a fourth type named Bag
.
Comparing Collections for Equality
A Collection
is equal to another Collection
of the same type. So a List
can be equal to a List
, a Set
can be equal to a Set
, and a Bag
can be equal to a Bag
. SortedSet
can be equal to another Set
(which may be a SortedSet
). SortedBag
can be equal to a Bag
(which may be a SortedBag
). We may need to convert a Collection
to the type we want to use for the equality test we are interested in.
Collection Equality Examples
For the test examples, I will use a simple Fruit
enum with APPLE
, BANANA
, and CHERRY
.
enum Fruit
{
APPLE, BANANA, CHERRY;
}
The following are some examples of testing equality for a List
.
@Test
public void listEquality()
{
MutableList<Fruit> list =
Lists.mutable.of(Fruit.BANANA, Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY);
// Equality of List is based on Type, Size, Contents, and Order
// List allows duplicates
// Order DOES impact the equality of a List
Assertions.assertEquals(
list,
List.of(Fruit.BANANA, Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY));
Assertions.assertNotEquals(
list,
List.of(Fruit.APPLE, Fruit.BANANA, Fruit.CHERRY));
Assertions.assertNotEquals(
list,
List.of(Fruit.CHERRY, Fruit.BANANA, Fruit.BANANA, Fruit.APPLE));
}
The following are some examples of testing equality for a Set
.
@Test
public void setEquality()
{
MutableSet<Fruit> set =
Sets.mutable.of(Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY);
// Equality of Set is based on Type, Size and Contents.
// Set allows unique elements only.
// Order DOES NOT impact the equality of a Set
Assertions.assertEquals(
set,
Sets.mutable.of(Fruit.APPLE, Fruit.BANANA, Fruit.CHERRY));
Assertions.assertEquals(
set,
Sets.mutable.of(Fruit.CHERRY, Fruit.BANANA, Fruit.APPLE));
Assertions.assertNotEquals(
set,
Sets.mutable.of(Fruit.APPLE, Fruit.BANANA));
Assertions.assertNotEquals(
set,
Sets.mutable.of(Fruit.BANANA, Fruit.CHERRY));
}
The following are some examples of testing equality for a Bag
.
@Test
public void bagEquality()
{
MutableBag<Fruit> bag =
Bags.mutable.of(Fruit.APPLE, Fruit.BANANA, Fruit.BANANA, Fruit.CHERRY);
// Equality of Bag is based on Type, Size and Contents
// Bag allows duplicates
// Order DOES NOT impact the equality of a Bag
Assertions.assertEquals(
bag,
Bags.mutable.of(Fruit.BANANA, Fruit.APPLE, Fruit.BANANA, Fruit.CHERRY));
Assertions.assertEquals(
bag,
Bags.mutable.of(Fruit.CHERRY, Fruit.BANANA, Fruit.APPLE, Fruit.BANANA));
Assertions.assertNotEquals(
bag,
Bags.mutable.of(Fruit.APPLE, Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY));
Assertions.assertNotEquals(
bag,
Bags.mutable.of(Fruit.APPLE, Fruit.BANANA, Fruit.CHERRY));
}
Converting Collections to Test for Equality
Sometimes we will need to convert a Collection
from one type to another in order to apply the equality comparison we are most interested in. The following table lists several types of equality tests we may want to perform on a Collection
of items, with the best Collection
type to use in those circumstances.
The Java Collection Framework does not have a Bag
type, so a Map
type would need to be used to simulate a Bag
.
Converting to a sorted List for Equality Test
Using Eclipse Collections MutableList
as a source type, we can convert the List
to a sorted List
using toSortedList
. There is no SortedList
type, so sorting doesn’t actually change the type, but toSortedList
will create a sorted copy of the List
, so the source List
remains unchanged.
The following code copies a MutableList<Fruit>
to a new MutableList<Fruit>
and sorts it in natural order using toSortedList
.
@Test
public void convertToSortedListForEquality()
{
MutableList<Fruit> list =
Lists.mutable.of(Fruit.BANANA, Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY);
// Sorting the List we can more predictably compare the contents
List<Fruit> sortedList = list.toSortedList();
Assertions.assertEquals(
sortedList,
List.of(Fruit.APPLE, Fruit.BANANA, Fruit.BANANA, Fruit.CHERRY));
Assertions.assertNotEquals(
sortedList,
List.of(Fruit.CHERRY, Fruit.BANANA, Fruit.CHERRY, Fruit.APPLE));
}
Converting to a Set for Equality Test
Using Eclipse Collections MutableList
as a source type, we can convert the List
to a Set
using toSet
.
@Test
public void convertToSetForEquality()
{
MutableList<Fruit> list =
Lists.mutable.of(Fruit.BANANA, Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY);
// Using a Set we can test unique contents without caring for order
Set<Fruit> set = list.toSet();
Assertions.assertEquals(
set,
Set.of(Fruit.CHERRY, Fruit.BANANA, Fruit.APPLE));
Assertions.assertEquals(
set,
Set.of(Fruit.APPLE, Fruit.BANANA, Fruit.CHERRY));
Assertions.assertNotEquals(
set,
Set.of(Fruit.APPLE, Fruit.BANANA));
}
Converting to a Bag for Equality Test
Using Eclipse Collections MutableList
as a source type, we can convert the List
to a Bag
using toBag
.
@Test
public void convertToBagForEquality()
{
MutableList<Fruit> list =
Lists.mutable.of(Fruit.BANANA, Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY);
// Using a Bag we can test size and contents without caring about order
Bag<Fruit> bag = list.toBag();
Assertions.assertEquals(
bag,
Bags.mutable.of(Fruit.CHERRY, Fruit.BANANA, Fruit.BANANA, Fruit.APPLE));
Assertions.assertNotEquals(
bag,
Bags.mutable.of(Fruit.CHERRY, Fruit.BANANA, Fruit.CHERRY, Fruit.APPLE));
}
I often find Bag
is the most useful Collection
type to use for equality testing, unless I care explicitly about the order.
What if I don’t have a Bag
?
My recommendation would be to download Eclipse Collections and get yourself a reusable Bag
for your Fruit
! 🍎🍌🍒
If you can’t afford a Bag
type in your code base for some reason, you can convert the List
to a Map
as follows. It’s just a bit more verbose.
@Test
public void convertToMapForEquality()
{
List<Fruit> list =
List.of(Fruit.BANANA, Fruit.BANANA, Fruit.APPLE, Fruit.CHERRY);
// Converting to Map we can also test size and contents without order
Map<Fruit, Long> simulatedBag = list.stream()
.collect(Collectors.groupingBy(each -> each, Collectors.counting()));
Assertions.assertEquals(
simulatedBag,
Map.of(Fruit.BANANA, 2L, Fruit.CHERRY, 1L, Fruit.APPLE, 1L));
Assertions.assertNotEquals(
simulatedBag,
Map.of(Fruit.BANANA, 1L, Fruit.CHERRY, 2L, Fruit.APPLE, 1L));
}
Summary
Comparing two Collections using equals
tests a bunch of things simultaneously. Using equals
will test the type, the size, the contents, and sometimes the order of the contents, depending on the equals contract of a particular type. Knowing when to use equals
with two instances of List
, Set
, Bag
, or Map
can help you write more concise and meaningful assertions. Using equals
is better than just testing size
, and less lines of code if you have to call contains
multiple times. Sometimes it helps to convert a particular Collection
to another type to test for equality, depending on what you are looking to test.
Thank you for reading, and I hope you find this blog useful!
I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.