不覆盖equals方法,类的每个实例都只与它自身相等。如果满足了以下任何一个条件,就正是所期望的结果:
需要覆盖equals的情况:如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为。
覆盖equals方法的时候需要遵守的通用约定(等价关系):
里氏替换原则:一个类型的任何重要属性也将适用于它的子类型,因此为该类型编写的任何方法,在它的子类型上也应该同样运行得很好。
结合这些要求,实现高质量equals方法的诀窍:
还有一些告诫:
在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。
Object规范:
如果hashCode方法为不相等的对象产生了很多相等的散列码,那么散列码相等的这些对象都被映射到同一个散列桶中,会是散列表退化成链表,极大的影响了散列表的性能。
一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。
在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息。无论是否制定输出的格式,都应该在文档中明确地表明你的意图。无论是否指定格式,都为toString返回值中包含的所有信息,提供一种编程式的访问途径。
Cloneable接口的目的是作为对象的一个mixin接口,表明这样的对象允许克隆。但是它缺少一个clone方法,Object的clone方法是受保护的。Cloneable接口决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。
如果实现Cloneable接口是要对某个类起到作用,类和它的所有超类都必须遵守一个相当复杂、不可实施的,并且基本上没有文档说明的协议,由此得到一种语言之外的机制:无需调用构造器就可以创建对象。
拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构,这个过程没有调用构造器。
如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。如果类的所有超类都遵守这条规则,那么调用super.clone最终会调用Object的clone方法,从而创建出正确类的实例。
实际上,clone方法就是另一个构造器;你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。
如果专门为了继承而去设计一个clone方法,那就应该模拟Object.clone的行为:它应该被声明为protected,抛出CloneNotSupportedException,并且该类不应该实现Cloneable接口。这样可以使子类具有实现或者不实现Cloneable接口的自由。还有,如果决定用线程安全的类实现Cloneable接口,那么要记得它的clone方法必须实现很好的同步。
Cloneable具有上述这么多的问题,可以肯定的说,其他的接口都不应该扩展这个接口,为了继承而设计的类也不应该实现这个接口,对于一个为了继承而设计的类,如果你未能提供行为良好的受保护的clone方法,它的子类就不可能实现Cloneable接口。
compareTo方法是Comparable接口中唯一的方法,compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较。类实现了Comparable接口,就表明它的实例具有内在的排序关系。
一旦实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。Java平台类库中的所有值类都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母排序、按数值顺序或者按年代排序,那就应该坚决考虑实现这个接口:
public interface Comparable<T> { int compareTo(T t); }
就好像违反了hashCode约定的类会破坏其他依赖于散列做法的类一样,违反compareTo约定的类也会破坏其他依赖于比较关系的类。依赖于比较关系的类包括有序集合类TreeSet和TreeMap,以及工具类Collections和Arrays,他们内部包含有搜索和排序算法。
CompareTo方法中域的比较是顺序的比较,而不是等同性的比较。比较对象引用域可以使通过递归地调用compareTo方法来实现。如果一个域没有实现Comparable接口,或者你需要使用一个非标准的排序关系,就可以使用一个显式的Comparator来代替,或者编写自己的Comparator,或者使用已有的Comparator。