主要讨论的是object的非final的方法,当需要覆盖这些方法的时候,需要遵守的一些约定,
遵守约定的好处是为了给用到这些约定的类,可以让比如HashMap,HashSet能够工作
equals hashcode toString clone,还有不是Object的Comparable.compareTo()
第十条 覆盖equals时请遵守通用约定 (对象比较)
1.不覆盖equals的类的条件
1.类的每个实例本质上是唯一的,比如Thread
2.类没有必要提供逻辑相等的测试功能,regex.pattern,没必要检验是否是同一个正则表达式
3.超类覆盖了equals方法,超类的行为对于这个类也合适,大多数set实现都继承AbstractSet的equals方法
4.类是私有的或者是包级私有的,确定它的equals永远不会被调用,也可以为了规避风险,在equals方法里面
抛出异常
5.枚举类型或者实例受控的类(只存在一个实例),每个值只存在一个对象,因此object的equals方法也就满足了
2.覆盖equals的类的条件
1.类具有自己的逻辑相等,而且超类没有覆盖equals方法,通常是值类
3. 覆盖equals方法需要遵守的通用约定
1.自反性: 对于任何非null的引用x,x.equals(x)必须返回ture
1.自己和自己比较,如果不等,那么集合在判断时候存在这个元素的时候,就无法判断了
2.对称性: 对于任何非null的引用x和y,x.equals(y) == true 那么y.equals(x) ==true
1.违反对称性,查看代码 chapter3/sample10/CaseInsensitiveString
3.传递性: 对于任何非null的引用x、y和z,x.equals(y) == true 并且 y.equals(z) == true 那么 x.equals(z)== true
1.违反传递性,查看代码 chapter3/sample10/Point, chapter3/sample10/ColorPoint
2.我们无法在扩展可实例化的类的同时,既增加新的组件值,同时又保留equals约定,除非愿意
放弃面向对象所带来的优势
3.我们可以使用getClass()来扩展了类和增加新的值组件,同时保留equals约定,但是破坏了
里氏转换原则(父类出现的地方一定可以用它的子类去替代),权宜之计是使用复合,而不是继承
4.如果超类是一个抽象类,那么上述的问题都不存在
4.一致性: 性: 对于任何非null的引用x和y,还要equals的比较操作在对象中所用的信息没有被修改,多次
调用x.equals(y) 就会一致地返回true,或者一致地返回false
1.不要让equals依赖于不可靠的资源,比如 java.net.URL的equals,它依赖于url地址中的ip地址,ip地址可能
发生改变
5.非空性:对于任何非null的引用x,x.equals(null) 必须返回false
1.在equals方法中,obj==null这是不必要的,obj instanceof interface 如果obj==null ,不管interface是什么类型
都会返回false
4.实现高质量的equals的诀窍
1.使用==操作符检查 "参数是否为这个对象的引用"
2.使用instanceof操作符检查 "参数是否为正确的类型"
3.把参数转换成正确的类型
4.对于该类中每个关键域,检查参数中的域是否与该对象中对应的域相匹配
5.对于非double和float的基本数据类型,使用==;引用类型可以使用equals;float使用Float.compare;
double使用Double.compare;
6.优先比较最有可能不一致的域
7.编写完,需要问自己三个问题,它是对称的、传递的、一致性的
5.告诫
1.覆盖equals时总时覆盖hashcode
2.不要企图让equals过于智能(意思是不要过于寻找不必要的相同点)
3.不要将equals声明中的Object对象替换成其他的类型(根本没覆盖Object.equals)
4.不要轻易的覆盖equals方法,除非迫不得已
5.可以使用idea工具生成
第十一条,覆盖equals时总要覆盖hashcode(作用于散列表,比如HashMap的key)
1.在每个覆盖了equals方法的类中,都必须覆盖hashcode方法
2.约定
1.在应用程序的执行期间,只要对象的equals方法比较操作所用到的信息没有被修改,那么对同一个
对象的多次调用,hashcode方法都必须始终返回同一个值。在一个应用程序与另一个程序的执行过程
中,执行hashcode方法所返回的值可以不一致
2.如果两个对象根据equals(Object)方法比较相等,那么调用这两个对象中的hashcode方法都必须产生相同
样的整数结果
3.如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象的hashcode方法,则不一定
要求hashcode方法必须产生不同的结果。但程序员应该知道,给不相等的对象产生截然不同的整数结果,
有可能提高散列表的性能
3.实现高质量的hashcode的诀窍
1.定义一个int类型的result变量用于返回hashcode
2.对于类的每个实例都使用这个公式去计算:
result = 31resultType.hashcode(x)
Type是指x域的类型,比如Integer.hashcode(x),如果是数组每个重要元素都使用上述的公式,如果都重要
可以使用Arrays.hashcode(x)
3.返回result
4.也可以使用Objects.hashcode(x,y,z),会创建数组导致性能低一点
5.对于一个不可变的类,可以缓存hashcode,不用每次调用都需要计算hashcode
6.不要试图从散列码计算中排除掉一个对象的关键域来提高性能
7.不要对hashcode的返回值做出具体的规定,因此客户端无法理所当然地依赖它,这样为修改
提供了灵活性
8.可以使用Idea提供的生成方法
第十二条,始终要覆盖toString (描述类的信息)
1.提供好的toString实现可以使类用起来更加舒适,使用了这个类的系统也更易于调试,比如
控制台打印这个对象
2.约定
1.建议所有的子类都覆盖这个方法,虽然这个约定并不是很重要,但这是一个好的建议
3.在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息
4.无论是否决定指定格式,都应该在文档中明确地表明你的意图,指定格式会失去了灵活性
在未来的版本中,要一直坚持这个格式,因为别人可能根据格式解析toString()的内容
5.为toString返回值中包含的所有信息提供一种可以通过编程访问的途径,比如getter,否则
使用这个对象的程序员可能去解析这个toString获取想要的值
6.静态工具类中编写toString方法没有意思;也不要在大多数枚举类型中编写toString,因为java
已经提供了非常完美的方法;在所有子类共享通用字符串表示法的抽象类中,一定要编写一
个toString方法,大多数集合实现中的toString方法都继承自抽象的集合
第十三条,谨慎的覆盖clone (复制对象)
1.虽然规范中没有明确的指出,事实上,实现cloneable接口的类是为了提供一个功能适当的公有的clone
方法,他无需调用构造器就可以创建对象
2.约定
1.对于任何对象x,x.clone != x 这条不是绝对的要求
2.对于任何对象x,x.clone.getClass() ==x.getClass() 这条不是绝对的要求
3.对于任何对象x,x.clone.equals(x) == ture 这条不是绝对的要求
4.按照约定,这个返回的对象应该通过调用super.clone获得,如果类以及超类遵守这一个约定,那么
x.clone.getClass() ==x.getClass()
如果超类不遵守,则子类调用super.clone时就会得到错误的类
3.对super.clone方法的调用应当包含在一个try-catch当中,Object.clone会抛出cloneNotSupportedException
4.不可变的类永远都不应该提供clone方法,避免不必要的克隆
5.如果对象包含引用可变的对象,使用简单的克隆会导致灾难性的后果(浅拷贝)
6.cloneable的架构与引用可变对象的final域的正常用法是不相兼容的
7.公有的clone方法应该省略throws声明,因为不抛出异常的方法使用更加的方便
8.对象拷贝的更好的方法是提供一个拷贝构造器或者拷贝工厂,比如 new TreeSet<>(Set),Yum newInstance(Yum yum)
9.最好利用clone 拷贝数组,但数组里面的引用可变的对象是相同的(浅拷贝)
第十四条,考虑实现Comparable接口 (排序)
1.java中的所有值类,枚举类都实现了Comparable接口
2.将这个对象与指定的对象进行比较,当该对象小于、等于或大于指定对象的时候。分别返回一个负整数、
零或者整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException
3.约定
1.实现者必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
2.实现者还必须确保这个比较关系是可传递的(x.compareTo(y) && y.compareTo(z))暗示了x.compareTo(z)
3.最后,实现者必须确保x.compareTo(y) ==0 暗示sgn(x.compareTo(y)) == sgn(y.compareTo(x))
4.强烈建议(x.compareTo(y) ==0) ==(x.equals(y)),但这不是必须的,一般来说违反了这个条件都应该明确地
说明,BigDecimal的equals和compareTo方法不一致
4.无法在用新的值组件扩展可实例化的类时,同时保持compareTo约定,除非愿意放弃面向对象的抽象优势,
建议使用复合,而不是继承去扩展
5.java7 中所有的包装类都提供了compare方法,在compareTo方法中使用关系操作符< 和> 是非常烦琐的,并且
容易出错,因此不建议使用
6.优先从最关键的域开始比较,相等则继续下一个域比较,直到比较完所有的关键域
7.java8中Comparator接口配置了一组比较器构造方法,排序可能速度慢10%,使用静态导入定义比较器,
看代码 chapter3/sample14/PhoneNumber
网友评论