第11条.覆盖equals时总要覆盖hashCode
在每个覆盖equals方法的类中,都必须覆盖hashCode方法。否则可能会导致该类无法结合所有基于散列的集合一起正常运作(包括HashMap和HashSet),以下是约定的内容:
1)在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用,hashCode方法必须始终返回同一个值。在一个应用程序与另一个程序的执行过程中,执行hashCode方法所返回的值可以不一致。
2)如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中的hashCode方法都必须产生同样的整数结果。
3)如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中的hashCode方法,则不一定要求hashCode方法必须产生不同的结果。但是给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
因没有覆盖hashCode而违反的关键约定是第二条:相等的对象必须具有相等的散列码。因为两个截然不同的实例在逻辑上可能是相等的,但是根据Object类的hashCode方法,它们仅仅是两个没有任何共同之处的对象。因此,对象的hashCode方法返回两个看起来是随机的整数,而不是要求的相等的整数。
理想情况下,散列函数应该把集中不相等的实例均匀地分布到所有可能的int值上。可以根据以下方法相对接近理想情形:
1.a.为该域计算int类型的散列码c。
1).如果该域是基本类型,则计算Type.hashCode(f)。
2).如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归调用 hashCode.
3).如果该域是一个数组,则要把每一个元素当做单独的域来处理。也就是说,递归地应用上述规则,对每个重要元素计算一个散列码,如果数组域中的所有元素都很重要,可以使用Arrays.hashCode方法。
b.按照下面的公式,把a中计算得到的散列码c合并到result中.
result = 31*result + c;
package com.example.ownlearn;
public class PhoneNumber {
private final int areaCode,prefix,lineNum;
public PhoneNumber(int areaCode,int prefix,int lineNum){
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNum = lineNum;
}
@Override
public boolean equals(Object obj) {
if(obj == this)
return true;
PhoneNumber ph = (PhoneNumber) obj;
return ph.prefix == prefix && ph.lineNum == lineNum && ph.areaCode == areaCode;
}
@Override
public int hashCode() {
int result = Integer.hashCode(areaCode);
result = 31 * result + Integer.hashCode(prefix);
result = 31 * result + Integer.hashCode(lineNum);
return result;
}
}
2.我们也可以采用Object类的静态方法,只不过运行速度更慢一些。
@Override
public int hashCode() {
return Objects.hash(lineNum,prefix,areaCode);
}
不要试图从散列码计算中排除掉一个对象的关键域来提高性能。
不要对hashCode方法的返回值做出具体的规定。
第12条.始终要覆盖toString
Object提供的toString方法的一个实现,返回的是类的名称@散列码的无符号十六进制表示。提供好的toString实现可以使类用起来更加舒适,使用了这个类的系统也更易于调试。
第13条.谨慎地覆盖clone
1.不可变的类永远都不应该提供clone方法。
2.必须确保它不会伤害到原始对象,并确保正确地创建被克隆对象中的约束条件。
package com.example.ownlearn;
import java.util.Arrays;
import java.util.EmptyStackException;
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
public Object pop(){
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;
return result;
}
private void ensureCapacity(){
if(elements.length == size)
elements = Arrays.copyOf(elements,2 * size +1);
}
@Override
protected Stack clone() throws CloneNotSupportedException {
Stack result = (Stack)super.clone();
result.elements = elements.clone();
return result;
}
}
为了使Stack类中的clone方法正常工作,它必须要拷贝栈的内部信息。我们不需要将 elements.clone()的结果转换成Object[]。在数组上用clone返回的数组,其编译时的类型与被克隆数组的类型相同。复制功能最好由构造器或者工厂提供,但是数组例外,最好利用clone方法复制数组。
第14条.考虑实现Comparable接口
compareTo方法的通用约定:
符号sgn(expression)表示数学中的signum函数,它根据表达式的值为负值、零和正值,分别返回-1、0或1.
1.实现者必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
2.实现者必须确保这个比较关系是可传递的:(x.compareTo(y) >0 && y.compareTo(z)>0) 暗示着x.compareTo(z) >0
private static final Comparator<PhoneNumber> COMPARATOR =
Comparator.comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
public int compareTo(PhoneNumber pn){
return COMPARATOR.compare(this,pn);
}