Effective Java(二)
对于所有对象都通用的方法
Object是一个具体类,但是设计它的主要目的是为了拓展,它的所有非final方法(equals,hashCode,toString,clone和finalize)都有明确的通用约定,因为它们被设计成是要被覆盖的。所以,任何一个类在覆盖这些方法的时候,都有责任遵守这些规定。
1. equals
在如下情况下不覆盖或者是确保永远不被调用
- 类的每个实例本质上是唯一的
- 不关心类是否提供了“逻辑相等”的测试功能
- 超类已经覆盖了equals,从超类继承而来的行为对子类也是合适的
- 类是私有的或是包级私有的,确定equals方法永远不会被调用,这种情况下应该覆盖equals方法,以防它会被意外调用
应该覆盖equals的情况:
- 需要知道逻辑上是否相等,而不是引用是否指向同一个对象的时候。类为“值类”
覆盖equals时所需要满足的条件:
- 自反性:每个对象都等于自己
- 对称性:两个对象双方相等。
在编写自己的类的时候要特别注意,本类重写的equals操作默认是知道其他复合类的信息的,但是被复合类可能不知道本类的信息。这样就很容易不满足传递性,导致实现出现异常。 - 传递性:多个对象相等的时候,相等可传递。
我们无法在扩展可实例化类的同时,既增加新的值组件,同时又保留equals约定,除非放弃面向对象的抽象所带来的优势。
如果使用getClass测试来替换instanceOf测试,可以扩展可实例化类和增加新的值组件,但是这样操作所造成的结果是不可接受的:
getClass判断的是类的绝对类型,而instanceOf判断的是类的类型以及是否继承于相同的父类。所以getClass要更加严格。而“里氏替换原则”认为一个类型的任何重要属性也将适用于它的子类型。如果父类采用的是getClass方法写成的equals方法,则其子类无论值是什么,都将会返回false。
权宜之计:使用复合而不是继承。 - 一致性:无论多少次比较,返回结果要一致。
无论类是否可变,都不要使equals方法依赖于不可靠的资源,比如对于IP地址,时间的依赖。 - 对于所有非null的引用,绝不等于null;
没有必要对于equals方法提供显式的判断非空测试,而应该在进行转换之前(参数一定是Object类型)使用instanceOf检查是否为正确的类型。
实现高质量equals方法的诀窍:
- 使用“==”操作符检查“参数是否为这个对象的引用”,这是一种性能优化,如果比较操作很昂贵,就值得这么去做。
- 使用“instanceOf”检查“参数是否是正确的类型”
- 使用instanceOf检查之后,把对象转换为正确的类型。
- 对于每个关键域,检查参数中的域是否与该对象中的域相匹配。
- 编写完equals方法之后,问自己:是否满足:对称性,传递性,一致性。
告诫:
- 覆盖equals时总要覆盖hashCode
- 不要企图使equals太智能
- 不要将声明中的Object对象替换成其他的类型。
2. 覆盖equals时总要覆盖hashCode
不这样做的话就会违反Object.hashCode的通用规约:相等的对象一定有相同的散列码(即使是在逻辑上相等),从而导致该类无法结合所有基于散列的集合一起工作,包括HashMap,HashSet,Hashtable等。
始终要覆盖toString
提供一个“简洁的,但信息丰富,并且易于阅读的表达方式”,无论是否为toString指定格式,都要在文档注释里为toString返回值中包含的所有信息,提供一种编程式的访问途径
谨慎覆盖clone
如果真的要copy对象,可以使用拷贝构造器(copy constructor)或拷贝工厂(copy factory)。比起调用clone方法,这样做风险更小。
考虑实现comparable接口
compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较,除此之外,它与Object的equals方法具有相似的特征,他还是个泛型。类实现了Comparable接口,就表明他的实例具有内在的排序关系(natural ordering)。
comparable接口用于排序,所有值类型的类都可以考虑实现Comparable接口。