第三章_对所有对象都通用的方法_effective java

第三章 对所有对象都通用的方法

第八条:覆盖equals时请遵守通用约定

在以下情况下,不需要覆盖equals:

  • 类的每个实例本质上都是唯一的:即代表实体的类而不是值的类,比如Thread
  • 不关心类是否提供了“逻辑相等”的测试功能:如Random
  • 超类已经覆盖了equals,并且从超类继承过来的行为是合理的:比如HashMap从AbstractMap继承的equals。
  • 类是私有的,确定equals不会被调用

当类具有自己特定的逻辑相等时,需要覆盖equals,这是需要遵守以下约定:

  • 自反性:对于任何非null的x,x.equals(x)
  • 对称性:x.equals(y) 等价于 y.equals(x)
  • 传递性:x.euqals(y),y.equals(z)推出x.equals(z)
  • 一致性:在x,y均未改变的情况下,多次调用x.equals(y)结果相同
  • 非空性:任何x!=null,满足x.equals(null)=false

这些要求最容易被破坏的情况其实是比较的时候x,y不是同一个类的情况(有可能x是y的子类这种),所以要非常注意跨类的equals

实现高质量equals的诀窍:

  1. 使用==操作检查参数是否为这个对象的引用,如果是则返回true。这种情况可以优化性能
  2. 使用instanceof操作符检查参数是否为正确类型
  3. 把参数转换成正确类型。因为2检查过,所以不会有错
  4. 对于类中的每个关键域,检查参数中的域是否与对象中对应域匹配。
  5. 完成后检查是否满足对称性,传递性和一致性

另外一些诀窍:

  • 覆盖equals总是要覆盖hashCode
  • 不要让equals过于智能
  • 不要将equals的参数从object变为其他类型。在覆盖equals时加上Override注解可以防止这种错误。

第九条:覆盖equals时总要覆盖hashcode

hashcode的约定内容如下:

  • 对象的equals方法用到的域没有修改,hashcode方法必须始终如一的返回同一个整数
  • 两个对象equals方法相等,则两个对象的hashcode结果也要相等
  • 一个好的hashcode倾向于为不equals的对象产生不同的hash编码

计算hashcode的诀窍在于:

  • 对equals用到的所有域做计算,同时排除equals没用到的域。虽然只对equals用到的部分域做计算能提高性能,但是这个结果可能带来更多的hash冲突
  • 如果一个类是不可变的,同时计算hashcode开销比较大,可以考虑把hashcode存起来并且做延迟加载

第十条:始终要覆盖tostring

这一条没有上两条那么严格,但是也是一个好习惯。并且注意tostraing时的格式设计。因为print函数会隐式的调用tostring,你设计的格式会影响到后续的输出处理log处理等。最好在toString时有文档。

第十一条:谨慎的覆盖clone

默认情况下,clone是浅拷贝。参考文献

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Person implements Cloneable{
private int age ;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Person() {}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}

person.clone后name是同一个:

另外自己试了一下,对于list拷贝,遍历循环复制,使用List实现类的拷贝构造方法,使用list.addAll()方法,使用System.arraycopy()方法,使用collections.copy都是浅拷贝,使用序列化方法是相对靠谱的深拷贝。参考文献

或者使用clone函数

clone函数是一个非常容易出错的函数。一般来讲,实现了Cloneable接口的类都应该用一个公有方法覆盖clone。此方法调用super.clone,然后修正任何需要修正的域。在对线程安全的类进行拷贝时记得同步。

其实可以用其他两种方法实现类似功能:

  • 拷贝构造器: public Sth (Sth sth)
  • 拷贝工厂: public static Sth newInstanc(Sth sth)

第十二条:考虑实现Comparable接口

  1. 确保所有的x、y满足 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。
  2. 确保比较关系是可传递的 x.compareTo(y)>0,y.compareTo(z)>0,那么x.compareTo(z)>0 。
  3. 若x.compareTo(y)==0, 则确保 sgn(z.compareTo(x)) == sgn(z.compareTo(y))。
  4. 建议(x.compareTo(y) == 0) == x.equals(y)。

依赖于比较关系的类包括:有序集合类 TreeSet和TreeMap 、工具类 Collections和Arrays,违反了compareTo方法约定会破坏这些依赖于比较关系的类 他们内部含有搜索和排序算法。类似于hashCode 跟hashMap的关系

Compareable接口是参数化的,因此不必进行类型检查也不需要对参数进行类型转换。


本文采用创作共用保留署名-非商业-禁止演绎4.0国际许可证,欢迎转载,但转载请注明来自http://thousandhu.github.io,并保持转载后文章内容的完整。本人保留所有版权相关权利。

本文链接:http://thousandhu.github.io/2016/10/02/第三章-对所有对象都通用的方法-effective-java/