第三章:对于所有对象都通用的方法
(1)覆盖equal时,请遵守通用约定
在重写任何类的equals方法是必须遵循以下几点:
1、对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。
2、反射性:x.equals(x)必须返回是“true”。
3、类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。
4、还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。
5、任何情况下,x.equals(null),永远返回是“false”;
在实现高质量equals方法的诀窍:
1,使用= = 操作符检查“参数是否为这个对象的引用”。如果是,则返回true。
2. 使用instanceof 操作符(如果的子类都拥有统一的语义)检查"参数是否为正确类型“,"正确类型"是指equals方法所在的类
3. 把参数转换成正确的类型
4. 对于该类的每个"关键"域,检查参数中的域是否与该对象中的域匹配。使用= = 比较基本类型域,使用equals比较对象域。
5.当你编写完equals方法以后,问自己:它是否是对称的,传递的,一致的。
(1)覆盖equal时总要覆盖hashCode
当我们向一个集合中添加某个元素,集合会首先调用hashCode方法,这样就可以直接定位它所存储的位置,若该处没有其他元素,则直接保存。若该处已经有元素存在,就调用equals方法来匹配这两个元素是否相同,相同则不存,不同则散列到其他位置
对于hashCode,我们应该遵循如下规则:
1. 在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须始终如一地返回同一个整数。
2. 如果两个对象根据equals(Object o)方法是相等的,则调用这两个对象中任一对象的hashCode方法必须产生相同的整数结果。
3. 如果两个对象根据equals(Object o)方法是不相等的,则调用这两个对象中任一个对象的hashCode方法,不要求产生不同的整数结果。但如果能不同,则可能提高散列表的性能。
因没有覆盖hashcCode而违反的关键约定是第二条,会导致两个相等的实例具有不同的的散列码
至于euqals 和hashCode两者之间的关联关系,我们只需要记住如下即可:
如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等
编写hashCode:
1.把某个非零的常数值,比如说17,保存在一个result 名字的int类型变量中
2.对于对象中每个关键域(指equals方法中涉及的每个域) 完成两个步骤:(1)为该域计算int类型的散列码c (2)按照 result = 31 *result + c 公式进行合并处理
3.返回result
注意:(1)在散列码计算过程中,如果一个域的值可以通过其它域计算出来,则计算hashCode不用计算它。(2)必须要排除equals比较计算中没有用到的任何域
(3)始终要要覆盖toString
toString方法用于 返回表示对象值的字符串,在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息
编写toString:
1.决定是否在文档中指定返回值的格式
2.无论决定是否指定返回值的格式,都应该在文档中明确表明你的意图
3.无论决定是否指定返回值的格式,都为toString返回值中包含的所有信息,提供一种编程式的访问途径(即在类中提供它们的访问方法)
(4)谨慎地覆盖clone
当拷贝变量时,原始变量与拷贝变量引用同一个对象,这就是说改变一个不安看所引用的对象将会对另一个对象产生影响。拷贝的目的是为了,是拷贝的对象 与原对象相互独立,各自改变状态而相互不影响
clone类必须要实现Cloneable接口,Cloneable接口是不包含任何方法的!它决定Object中受保护的的clone方法实现的行为:如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常
编写Clone方法要注意的:
1. 默认的克隆操作是浅拷贝,它并没有克隆包含在对象中的内部对象,如果对象中包含的域引用了可变的对象,使用默认clone方法可能会导致灾难性的后果,clone方法就是一个构造器:你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件,对于对象中包含的域引用了可变的对象,要实现深拷贝
2.所有拷贝都要实现Cloneable 接口,并且首先调用super.clone().如果要实现深拷贝,必须克隆所有可变的实例域。这意味者要拷贝任何包含内部"深层结构"的可变对象,并用指向新对象的引用代替原来指向这些对象的引用,往往通过递归地调用clone来完成但这通常不是最佳方法。如果该u对象只包含基本类型的域或者指向不可变对象的引用,那么多半情况下是没有域需要修正
3.克隆复杂对象的的方法:先调用super.clone(),然后把结果对象中的所有域都设置成它们的空白状态,然后调用高层的方法来重新产生对象的状态
4.在大部分情况下,没有必要搞得这么复杂。克隆的应用并不没有那么普遍
编写Clone方法要注意的:
1.compareTo方法的通用约定与 equals方法相似,域equals不同的是,跨越不同类的时候, compareTo方法可以不做比较,如果两个被比较对象引用不同的类的对象,compareTo可以抛出ClassCastException异常
2.违反 compareTo约定的类也会破坏其它依赖于比较关系的类。依赖比较关系的类包括有序集合类TreeSet和TreeMap,以及工具类Collections和Arrays,它们内部包含有搜索和排序算法
3.如果一个人类有多个关键域,你必须从最关键的域开始,逐步进行到所有的重要域。如果某个域的比较产生了非零的结果(零代表相等),则整个比较操作结束,并返回该结果。如果最关键域相等,则进一步比较次最关键的域,以此类推。如果所有的域都是相等的,则对象就是相等的,并返回零
4. 如果一个域并没有实现Comparable接口,或者你需要一个非标准的排序关系,就可以使用一个显式的Comparator来代替Comparator可以看成一种算法的实现,将算法和数据分离,Comparator也可以在下面两种环境下使用:
(1)类的设计师没有考虑到比较问题而没有实现Comparable,可以通过Comparator来实现排序而不必改变对象本身
(2)可以使用多种排序标准,指定多种排序方法来提高灵活性,比如升序、降序