ArrayList.containsAll未调用自定义equals方法致JUnit测试失败
先来看你的测试代码和类定义:
测试代码
List<KGramPostingsEntry> a = new ArrayList<KGramPostingsEntry>(); List<KGramPostingsEntry> b = new ArrayList<KGramPostingsEntry>(); KGramPostingsEntry entry = new KGramPostingsEntry(1); a.add(entry); entry = new KGramPostingsEntry(1); b.add(entry); assertTrue(a.containsAll(b));
部分KGramPostingsEntry类代码
package ir; public class KGramPostingsEntry { int tokenID; public KGramPostingsEntry(int tokenID) { this.tokenID = tokenID; } // 缺少重写的equals和hashCode方法 }
问题根源
你遇到的断言失败,核心原因是**KGramPostingsEntry没有重写Object类的equals()方法**。
Java中所有类默认继承自Object,而Object的equals()方法默认判断的是两个对象的引用是否完全相同(也就是是否是同一个对象实例)。你的测试里创建了两个不同的KGramPostingsEntry对象,虽然它们的tokenID都是1,但它们是内存中两个独立的实例,所以ArrayList.containsAll()会认为b中的元素不在a里,导致断言失败。
解决方案
要让containsAll()按照你期望的“内容相等”来判断,必须在KGramPostingsEntry中重写equals()方法,同时按照Java规范,最好一起重写hashCode()方法(如果后续用到HashMap、HashSet这类依赖hashCode的集合,这一点尤为重要)。
修改后的KGramPostingsEntry类示例:
package ir; public class KGramPostingsEntry { int tokenID; public KGramPostingsEntry(int tokenID) { this.tokenID = tokenID; } @Override public boolean equals(Object o) { // 先判断引用是否相同 if (this == o) return true; // 判断是否是同一类型 if (o == null || getClass() != o.getClass()) return false; // 强转后比较内容 KGramPostingsEntry that = (KGramPostingsEntry) o; return tokenID == that.tokenID; } @Override public int hashCode() { // 基于tokenID生成hash值,保证equals相等的对象hashCode一致 return Integer.hashCode(tokenID); } }
为什么要重写hashCode?
根据Java语言规范:如果两个对象通过equals()方法判断为相等,那么它们的hashCode()必须返回相同的值。如果只重写equals()不重写hashCode(),当你把KGramPostingsEntry实例放入HashSet或者作为HashMap的键时,会出现逻辑错误(比如无法正确找到已存在的元素)。
修改完成后,你的JUnit测试中的assertTrue(a.containsAll(b))就会通过,因为此时containsAll()会调用你自定义的equals()方法,判断两个实例的tokenID是否相等,而不是判断引用是否相同。
内容的提问来源于stack exchange,提问作者Sahand

