覆盖equals方法和hashCode方法看似简单,但其实不然,如果没有按照jdk的通用规范去覆盖,那么基于这些约定的类将可能无法正常工作,例如基于散列的集合类HashMap和HashSet.class="Apple-converted-space">?
? 对于值类,我们通常需要覆盖Object.equals方法,因为我们希望通过equals方法知道它们在逻辑上是否相等.相应的这个类的实例可以被用作map的key,或者set的元素的时候才会表现出预期的行为. 对于"值类",枚举是个例外,因为枚举的每个值都是个单例.?
? 在覆盖equals时,必须遵守JavaSE Object的规范:自反性(reflective), 对称性 (symmetric),传递性(transitive),一致性(consistent),对于任何非null的引用x, x.equals(null)返回false. 对于每个覆盖类equals的类,都应该有相应的单元测试去检查是否有没有违反上述约定。下面是个简单的例子:?
Java代码
?
- public?class?PhoneNumber?{??
- ??
- ????private?int?countryCode;??
- ????private?String?nationalNumber;??
- ??????
- ????public?PhoneNumber(){??
- ????????super();??
- ????}??
- ??????
- ????public?PhoneNumber(int?countryCode,?String?nationalNumber)?{??
- ????????super();??
- ????????this.countryCode?=?countryCode;??
- ????????this.nationalNumber?=?nationalNumber;??
- ????}??
- ?????
- ?
- ??
- ????public?int?getCountryCode()?{??
- ????????return?countryCode;??
- ????}??
- ?????
- ?
- ??
- ????public?void?setCountryCode(int?countryCode)?{??
- ????????this.countryCode?=?countryCode;??
- ????}??
- ??????
- ?????
- ?
- ??
- ????public?String?getNationalNumber()?{??
- ????????return?nationalNumber;??
- ????}??
- ?????
- ?
- ??
- ????public?void?setNationalNumber(String?nationalNumber)?{??
- ????????this.nationalNumber?=?nationalNumber;??
- ????}??
- ????@Override??
- ????public?boolean?equals(Object?o){??
- ????????if(this?==?o){??
- ????????????return?true;??
- ????????}??
- ????????if(!(o?instanceof?PhoneNumber)){??
- ????????????return?false;??
- ????????}??
- ????????PhoneNumber?pn?=?(PhoneNumber)?o;??
- ????????return?this.countryCode?==?pn.getCountryCode()??
- ????????????????&&?(?this.nationalNumber?==?null????pn.nationalNumber?==?null?:?this.nationalNumber.equals(pn.nationalNumber));??
- ????}?????
- }??
- ??
- import?static?org.junit.Assert.*;??
- ??
- import?org.junit.Test;??
- ??
- public?class?PhoneNumberTest?{??
- ??
- ????@Test??
- ????public?void?testEqualsReflexive(){??
- ????????PhoneNumber?pn1?=?new?PhoneNumber(86,?"12345");??
- ????????assertTrue(pn1.equals(pn1));??
- ????????PhoneNumber?pn2?=?new?PhoneNumber();??
- ????????assertTrue(pn2.equals(pn2));??
- ????}??
- ??????
- ????@Test??
- ????public?void?testEqualsSymmetric(){??
- ????????PhoneNumber?pn1?=?new?PhoneNumber(86,?"12345");??
- ????????PhoneNumber?pn2?=?new?PhoneNumber(86,?"12345");??
- ????????assertEquals(pn1.equals(pn2),?pn2.equals(pn1));??
- ????}??
- ??????
- ??????
- ????@Test??
- ????public?void?testEqualsTransitive(){??
- ????????PhoneNumber?pn1?=?new?PhoneNumber(86,?"12345");??
- ????????PhoneNumber?pn2?=?new?PhoneNumber(86,?"12345");??
- ????????PhoneNumber?pn3?=?new?PhoneNumber(86,?new?String("12345"));??
- ????????assertTrue(pn1.equals(pn2));??
- ????????assertTrue(pn2.equals(pn3));??
- ????????assertTrue(pn1.equals(pn3));??
- ????}??
- ??????
- ????@Test??
- ????public?void?testEqualsConsistent(){??
- ????????PhoneNumber?pn1?=?new?PhoneNumber(86,?"12345");??
- ????????PhoneNumber?pn2?=?new?PhoneNumber(86,?"12345");??
- ????????for(int?i=0;?i<10?;?i++){??
- ????????????assertTrue(pn1.equals(pn2));??
- ????????}??
- ????}??
- ??????
- ????@Test??
- ????public?void?testEqualsWithNull(){??
- ????????PhoneNumber?pn1?=?new?PhoneNumber(86,?"12345");??
- ????????assertFalse(pn1.equals(null));??
- ????}??
- ??????
- }??
当然还有一些实现高质量equals方法的诀窍:?
?? 1. 使用==caozuofu.html" target="_blank">操作符检查"参数是否为正确的引用"?
?? 2. 使用instanceof检查类型?
?? 3. 把参数转化为正确的类型?
?? 4. 选择逻辑比较的关键域,注意比较的顺序,primitive的比较可以放在前面,或者最有可能不一致性的域?
?? 5. 如果有double,float类型,用Double.compare,Float.compare比较?
?? 6. 覆盖equals重要覆盖hashCode
?
如前文所述,在覆盖了equals方法的类中,也必须覆盖hashCode方法。否则违反了Object.hashCode的通用约定会导致该类无法和基于散列的集合(HashMap,HashSet和HashTable)一起正常使用。?
? 如下约定内容摘自Object规范:?
? 1. 在应用程序中,只要对象的euqals方法的比较操作所用的信息没有修改,那么对于同一个对象的调用多次hashCode,必须始终如一返回同一个哈希值。?
? 2. 如果两个对象通过equals比较相等,那么它们的哈希值相同。?
? 3. 如果两个对象通过euqals比较不等,他们的哈希值可能相同,取决于hashCode的实现,由此散列表的性能也会有区别。?
? 以前面的PhoneNumber类为例,编写了如下的测试用例:?
??
?
Java代码
?
- @Test??
- ??public?void?testHashCode(){??
- ????PhoneNumber?pn1?=?new?PhoneNumber(86,?"12345");??
- ????PhoneNumber?pn2?=?new?PhoneNumber(86,?"12345");??
- ????Map<PhoneNumber,String>?map?=?new?HashMap<PhoneNumber,String>();??
- ????map.put(pn1,?"12345");??
- ????assertNotNull(map.get(pn2));??
- ??}??
? 发现测试失败了,但是两个对象通过equals比较是相等的。由于并没有覆盖hashCode方法导致两个相等的对象不能获得相同的散列码。根据约定重写hashCode:?
Java代码
?
- @Override??
- public?int?hashCode(){??
- ????int?result?=?17;??
- ????result?=?31?*?result?+?countryCode;??
- ????if(nationalNumber?!=?null)??
- ????result?=?31?*?result?+?nationalNumber.hashCode();??
- ????return?result;??
- }??
??
? 好的散列函数通常倾向于"为不相等的对象产生不相等的散列码", 否则会引起冲突,使散列表想链表退化。计算是可以把冗余的域排除在外。注意不要试图从散列码计算中排除关键域来提高性能。?