章
目
录
学会如何在Java中比较两个List列表,以便判断两个列表包含完全相同的项目,而且每个列表项在两个列表中的出现次数必须相等。
1.使用Common Collections4的CollectionUtils.isEqualCollection()
要使用此API,请在Maven存储库中包含最新版本的commons-collections4。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
isEqualCollection() API在两个列表完全包含相同元素且具有完全相同基数(即:一个元素在两个列表中的出现次数完全相同)时返回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);
}
@Override
public int hash(String s) {
return s.toUpperCase().hashCode();
}
}
assertTrue(CollectionUtils.isEqualCollection(
Arrays.asList("a", "b"),
Arrays.asList("A", "B"),
new StringCaseEquator()));
2.使用Hamcrest的Matchers.containsInAnyOrder()
要使用此API,请在Maven存储库中包含最新版本的hamcrest-all。
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
</dependency>
containsInAnyOrder() API创建了一个用于可迭代对象的无序匹配器,该匹配器匹配两个列表的元素,忽略列表中元素的顺序。对于积极匹配,两个列表必须具有相同的大小,因此我们不需要显式比较大小。
List<String> list = Arrays.asList("a", "b", "c");
List<String> equalList = Arrays.asList("b", "c", "a");
assertThat(list, Matchers.containsInAnyOrder(equalList.toArray()));
此解决方案不支持列表项的自定义相等逻辑。
3.使用AssertJ的containsExactlyInAnyOrderElementsOf() API
首先添加Maven存储库中最新版本的assertj-core。
<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()方法来设计我们自己的解决方案。在以下代码中,我们正在迭代第一个列表,并从第二个列表中删除当前项目的第一个出现。迭代完成后,第二个列表必须为空。请注意,此方法使用ArrayList类上提供的remove() API。因此,如果使用不可变列表,请考虑将它们包装在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和纯粹的Java API的示例,可用于JUnit断言语句。