遵守equals和hashCode方法的通用约定 (1)
程序员文章站
2022-05-29 22:00:22
...
覆盖equals方法和hashCode方法看似简单,但其实不然,如果没有按照jdk的通用规范去覆盖,那么基于这些约定的类将可能无法正常工作,例如基于散列的集合类HashMap和HashSet.
对于值类,我们通常需要覆盖Object.equals方法,因为我们希望通过equals方法知道它们在逻辑上是否相等.相应的这个类的实例可以被用作map的key,或者set的元素的时候才会表现出预期的行为. 对于"值类",枚举是个例外,因为枚举的每个值都是个单例.
在覆盖equals时,必须遵守JavaSE Object的规范:自反性(reflective), 对称性 (symmetric),传递性(transitive),一致性(consistent),对于任何非null的引用x, x.equals(null)返回false. 对于每个覆盖类equals的类,都应该有相应的单元测试去检查是否有没有违反上述约定。下面是个简单的例子:
当然还有一些实现高质量equals方法的诀窍:
1. 使用==操作符检查"参数是否为正确的引用"
2. 使用instanceof检查类型
3. 把参数转化为正确的类型
4. 选择逻辑比较的关键域,注意比较的顺序,primitive的比较可以放在前面,或者最有可能不一致性的域
5. 如果有double,float类型,用Double.compare,Float.compare比较
6. 覆盖equals重要覆盖hashCode
对于值类,我们通常需要覆盖Object.equals方法,因为我们希望通过equals方法知道它们在逻辑上是否相等.相应的这个类的实例可以被用作map的key,或者set的元素的时候才会表现出预期的行为. 对于"值类",枚举是个例外,因为枚举的每个值都是个单例.
在覆盖equals时,必须遵守JavaSE Object的规范:自反性(reflective), 对称性 (symmetric),传递性(transitive),一致性(consistent),对于任何非null的引用x, x.equals(null)返回false. 对于每个覆盖类equals的类,都应该有相应的单元测试去检查是否有没有违反上述约定。下面是个简单的例子:
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; } /** * @return the countryCode */ public int getCountryCode() { return countryCode; } /** * @param countryCode the countryCode to set */ public void setCountryCode(int countryCode) { this.countryCode = countryCode; } /** * @return the nationalNumber */ public String getNationalNumber() { return nationalNumber; } /** * @param nationalNumber the nationalNumber to set */ 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. 使用==操作符检查"参数是否为正确的引用"
2. 使用instanceof检查类型
3. 把参数转化为正确的类型
4. 选择逻辑比较的关键域,注意比较的顺序,primitive的比较可以放在前面,或者最有可能不一致性的域
5. 如果有double,float类型,用Double.compare,Float.compare比较
6. 覆盖equals重要覆盖hashCode
推荐阅读
-
一文搞懂hashCode()和equals()方法的原理
-
java类中的equals、hashCode和toString方法
-
java实体类中equals和hashCode方法的重写
-
荐 equals和hashCode的约定(重写equals必须重写hashCode)
-
java中“==”和equals()的区别、hashCode()、为什么重写equals()方法必须重写hashCode()
-
遵守equals和hashCode方法的通用约定 (1)
-
关于Hibernate ORM映射实体通用hashCode与equals方法的实现
-
关于Hibernate ORM映射实体通用hashCode与equals方法的实现
-
一文搞懂hashCode()和equals()方法的原理
-
Object类和类中常用的方法:hashCode(),getClass(),toString(),equals(Object obj)(默认),clone();