Chapter 3 对于所有对象都通用的方法

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

什么时候应该覆盖equals()方法呢?
如果类具有逻辑相等的概念, 通常属于值类(value class)的情形.
例外: 实例受控的值类: 枚举, 一个值对应一个实例, 所以不需要覆盖equals.
覆盖equals方法的时候, 必须要遵守通用约定:

  • 自反性(reflexive): 对象必须等于其自身.
  • 对称性(symmetric): 任何两个对象关于它们是否相等的结果保持一致.
  • 传递性(transitive): 如果一个对象等于第二个对象, 第二个对象等于第三个对象, 则第一个对象一定等于第三个对象.
  • 一致性(consistent): 如果两个对象相等, 它们就必须始终保持相等, 除非它们被修改了.
  • 非空性(non-nullity): 所有的对象都必须不等于null.
  • 对于任何非null的引用值x,x.equals(null)必须返回false

第11条 覆盖equals时总要覆盖hashCode

在每个覆盖了equals方法的类中, 也必须覆盖hashCode方法.
如果不这样做的话, 就会违反Object.hashCode的通用约定, 从而导致该类无法结合所有基于散列的集合一起正常运作, 这样的集合包括HashMap, HashSetHashtable.
通用约定:

  • 程序执行期间, 只要对象的equals方法的比较操作所用到的信息没有被修改, 那么多次调用hashCode方法都必须始终如一地返回同一个整数.
    (在应用程序多次执行的过程中, 每次执行所返回的整数可以不一致.)
  • 如果两个对象根据equals比较相等, 那么hashCode结果应该相同.
  • 如果两个对象根据equals比较不相等, 则hashCode不一定要产生不同的整数结果.
    (但是不相等的对象产生不同的hashCode有可能提高散列表的性能. 一个好的散列函数通常倾向于为不相等的对象产生不相等的散列码.)

第12条 始终要覆盖toString

提供好的toString方法可以使类使用起来更加舒适, 更利于调试.
实践上, toString方法应该返回对象中所有感兴趣的信息.
在实现toString的时候, 必须要做出一个很重要的决定: 是否在文档中指定返回值的格式.

  • 好处: 标准, 明确, 适合人阅读, 容易在对象和它的字符串表示法之间来回转换.
  • 不足: 一旦指定, 就必须坚持这种格式, 如果要改变就会破坏原来的代码和数据.
    无论是否指定了格式, 都应该在文档中说明意图.

第13条 谨慎地覆盖clone

来自Object规范中的clone方法的通用约定:
创建和返回对象的一个拷贝. 这个拷贝的精确含义取决于该对象的类.
通常要求:

  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x)
    通常要求这三个表达式都为true, 但不是绝对.

第14条 考虑实现Comparable接口

compareTo方法是Comparable接口中唯一的方法, 允许进行等同性和顺序比较:
将对象与指定的对象进行比较, 当该对象小于, 等于或大于指定对象的时候, 分别返回一个负整数, 零或正整数.
compareTo施加的等同性测试, 也一定遵守相同于equals约定所施加的限制条件: 自反性, 对称性和传递性.
强烈建议(x.compareTo(y) == 0) == (x.equals(y)).
Java 8提供了一些comparator构造的方法, 比如comparingInt, thenComparingInt, comparing等, 可以链式组合使用.
由于compareTo方法并没有指定返回值的大小, 而只是指定了符号, 所以可以利用这一点进行简化.
反例: 不要用两个数相减的方法: 注意可能会溢出导致错误, 并且这样做并没有明显的性能改善.
-> 推荐用静态的Integer.compare方法或者comparingInt来构造Comparator.