在單元測試中,當期望結(jié)果(Expect)和實際結(jié)果(Actual)都是對象時,進行Assert.assertEquals判斷時,可能會不符合預期,我們認為符合預期的測試結(jié)果還是失敗,
主要還是對象equals比較不等導致的。
原因分析
如果選用junit框架,當調(diào)用Assert.assertEquals(expected, actual),最終會
調(diào)用對象當equals方法:
private static boolean isEquals(Object expected, Object actual) {
return expected.equals(actual);
}
我們知道,Object是所有類的父類,其equals方法實現(xiàn)
public boolean equals(Object obj) {
return (this == obj);
}
這就引出另一個問題,equals如何比較,equals和=有何不同?
this 和 obj都是對象的引用,也就是對于任何非空的參考值x和y ,當且僅當x和y引用相同的對象( x == y具有值true )時,該方法返回true 。
解決方法
重寫對象的equals方法。參見jdk官方文檔,為了避免重寫equals方法引入該對象的其它操作漏洞,一定要實現(xiàn)等價關(guān)系。
public boolean equals(Object obj)
equals方法在非空對象引用上實現(xiàn)等價關(guān)系:
自反性 :對于任何非空的參考值x ,x.equals(x)應該返回true 。
對稱性 :對于任何非空引用值x和y , x.equals(y)應該返回true當且僅當y.equals(x)回報true 。
傳遞性 :對于任何非空引用值x , y和z ,如果x.equals(y)回報true個y.equals(z)回報true ,然后x.equals(z)應該返回true 。
一致性 :對于任何非空引用值x和y ,多次調(diào)用x.equals(y)始終返回true或始終返回false ,沒有設(shè)置中使用的信息equals比較上的對象被修改。
對于任何非空的參考值x , x.equals(null)應該返回false 。
請注意,無論何時覆蓋該方法,通常需要覆蓋hashCode方法,以便維護hashCode方法的通用合同,該方法規(guī)定相等的對象必須具有相等的哈希碼。
hashCode的總合同是:
只要在執(zhí)行Java應用程序時多次在同一個對象上調(diào)用該方法, hashCode方法必須始終返回相同的整數(shù),前提是修改了對象中equals比較中的信息。 該整數(shù)不需要從一個應用程序的執(zhí)行到相同應用程序的另一個執(zhí)行保持一致。
如果根據(jù)equals(Object)方法兩個對象相等,則在兩個對象中的每個對象上調(diào)用hashCode方法必須產(chǎn)生相同的整數(shù)結(jié)果。
不要求如果兩個對象根據(jù)equals(java.lang.Object)方法不相等,那么在兩個對象中的每個對象上調(diào)用hashCode方法必須產(chǎn)生不同的整數(shù)結(jié)果。 但是,程序員應該意識到,為不等對象生成不同的整數(shù)結(jié)果可能會提高哈希表的性能。
盡可能多的合理實用,由類別Object定義的hashCode方法確實為不同對象返回不同的整數(shù)。 (這通常通過將對象的內(nèi)部地址轉(zhuǎn)換為整數(shù)來實現(xiàn),但Java的編程語言不需要此實現(xiàn)技術(shù)。)
哈希沖突是所有哈希函數(shù)無法完全避免的,只能盡量的減少沖突。所以也無法保證對于不同的對象,其hashCode完全不同。
具體實現(xiàn)
@Builder @Setter @Getter
public static class Tag extends Object {
private Integer id;
private String name;
private float confidence;
@Override
public int hashCode() {
return Objects.hash(this.id, this.name, this.confidence);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null) {
return false;
}
Tag tag = (Tag) obj;
if (tag.getId() == this.id
&& tag.getName().equals(this.name)
&& tag.getConfidence() == this.confidence) {
return true;
}
return false;
}