第3章 对于所有对象都通用的方法
8. 覆盖equals时请遵守通用约定
不覆盖equals
方法的情况下,类的每个实例都只与它自身相等。
如果满足了以下任何一个条件,这就正是所期望的结果:
- 类的每个实例本质上都是唯一的。
Thread
- 不关心类是否提供了逻辑相等的测试功能。
Random
从Object
类继承了equals
- 超类已经覆盖了
equals
,从超类继承过来的行为对于子类也是合适的。Set
从AbstractSet
继承equals
实现,List
从AbstractList
继承equals
实现,Map
从AbstractMap
继承equals
实现 - 类是私有的或是包级私有的,可以确定它的
equals
方法永远不会被调用。在这种情况下,无疑是应该覆盖equals
方法的,以防止它被意外调用:
@Override public boolean equals(Object o) {
throw new AssertionError(); // method is never called
}
如果类有自己特有的逻辑相等概念,而且超类没有覆盖equals
以实现期望的行为,这时我们就需要覆盖equals
方法。
equals
方法实现了等价关系:自反性,对称性,传递性,一致性,非空性
实现高质量equals
方法的诀窍:
- 使用==操作符检查参数是否为这个对象的引用
- 使用
instanceof
操作符检查参数是否为正确的类型 - 把参数转换为正确的类型
- 对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配
9.覆盖equals时总要覆盖hashCode
在每个覆盖了equals
方法的类中,也必须覆盖hashCode
方法,如果不这样做的话,就会违反Object.hashCode
的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap
、HashSet
、Hashtable
。
相等的对象必须具有相等的散列码。
@Override public int hashCode() { return 42; } // never use
每个对象都具有相同的散列码,每个对象都被映射到同一个散列桶中,使散列表退化为链表,使得本该线性时间运行的程序变成了以平方级时间在运行。
10.始终要覆盖toString
在实际应用中,toString
方法应该返回对象中包含的所有值得关注的信息。
11.谨慎地覆盖clone
Java1.5版本引入了协变返回类型(covariant return type)作为泛型,换句话说,目前覆盖方法的返回类型可以是被覆盖方法的返回类型的子类了。
clone方法就是另一个构造器,你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。clone架构与引用可变对象的final域的正常用法是不相兼容的。
/**
* Creates a shallow copy of this hashtable. All the structure of the
* hashtable itself is copied, but the keys and values are not cloned.
* This is a relatively expensive operation.
*
* @return a clone of the hashtable
*/
public synchronized Object clone() {
try {
Hashtable<?,?> t = (Hashtable<?,?>)super.clone();
t.table = new HashtableEntry<?,?>[table.length];
for (int i = table.length ; i-- > 0 ; ) {
t.table[i] = (table[i] != null)
? (HashtableEntry<?,?>) table[i].clone() : null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
另一个实现对象拷贝的好办法是提供一个拷贝构造器(copy constructor)或拷贝工厂(copy factory)。拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类。
基于接口的拷贝构造器和拷贝工厂(conversion constructor & conversion factory),允许用户选择拷贝的实现类型,而不是强迫客户接受原始的实现类型。
12.考虑实现Comparable接口
一旦类实现了Comparable
接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。付出很小的努力就可以获得非常强大的功能。事实上,Java平台类库中的所有值类都实现了Comparable
接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按年代排序,那你就应该坚决考虑实现这个接口:
public interface Comparable<T> {
int compareTo(T t);
}
compareTo
方法的通用约定与equals
方法相似。
比较整数型基本类型的域,可以使用关系操作符<
和>
。浮点域用Double.compare
或者Float.compare
,而不用关系操作符,浮点值没有遵守compareTo
的通用约定。对于数组域,则要把这些指导原则应用到每个元素上。