第9条:覆盖equals时 总要覆盖hashCode
在每个覆盖equals方法的类中,也必须覆盖hashCode方法。否则,会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,包括HashMap,HashSet,Hashtbale。
/**
* 覆盖equals时 总要覆盖hashCode
*/
public class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber(short areaCode, short prefix, short lineNumber) {
rangCheck(areaCode,999,"area code");
rangCheck(prefix,999,"prefix");
rangCheck(lineNumber,9999,"line number");
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
public static void rangCheck(int arg,int max,String name){
if (arg < 0||arg > max){
throw new IllegalArgumentException(name + ":" + arg );
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PhoneNumber9 that = (PhoneNumber9) o;
return areaCode == that.areaCode &&
prefix == that.prefix &&
lineNumber == that.lineNumber;
}
@Override
public int hashCode() {
return Objects.hash(areaCode, prefix, lineNumber);
}
/*
hashCode的三个约定:
1. 在程序执行期间,只要对象的equals所用的的信息没有修改,那么对这个同一个对象多次调用,hashCode方法必须始终如一的返回同一个整数,在同一个应用多次执行的过程中,
每次执行所hashCode返回的整数可以不一致
2. 如果两个对象根据equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
3. 如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。
技巧:
boolean ==>(f?1:0)
byte、short、char、int ==> (int)f
long ==> (int)(f^(f>>>32))
float ==> Float.floatToIntBits(f)
double ==> Double.doubleToLongBits(f)
object ==> object != null ? object.hashCode() : object
object [] ==> Arrays.hashCode(object)
*/
}
如果一个类是不可变的,并且计算散列码的开销很大,应该考虑把散列码缓存到对象内部而不是每次请求都重新计算散列码,如果这种类大多数对象会被用作散列键,应该在创建实例的时候计算散列码,否则可以选择延迟初始化散列码。
不要试图从散列码计算中排除掉一个对象的关键部分来提高性能。虽然这样做运行起来可能更快,但效果不见得好,在拥有大量实例的时候,忽略的域区别仍然非常大,但散列函数仍然把它们映射到同样的散列桶中,例如Java 1.2之前实现的String散列函数至多检查16个字符,对于像URL这样的大型集合,散列函数表现出病态的行为。