欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

遵守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的类,都应该有相应的单元测试去检查是否有没有违反上述约定。下面是个简单的例子:

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