Effective Java 学习-对于所有对象都通用的方法

Effective Java 学习-对于所有对象都通用的方法

左岸 175 2022-12-15

Effective Java 学习-创建和销毁对象

尽管 Object 是一个具体类,但设计它主要是为了扩展 它所有的非 final 方法(equals hashCode toString clone finalize )都有明确的通用约定( general contract), 因为它设计成是要被覆盖( overri de )的 任何个类,它在覆盖这些方法的时·候,都有责任遵守这些通用约定;如果不能做到这 点,其 依赖于这些约定的类(例如 HashMap HashSet )就无法结合该类起正常运作

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

什么时候不用覆盖equals方法

如果满足了以下任何 个条件,就可以

  1. 类的每个实例本质都是唯一的

对于代表活动实体而不是值(value)类来说确实如此,例如Thrad, object 提供的equals实习对于这些类来说就是正确的行为

  1. 类没有必要提供“逻辑相等” 的测试功能

例如 java.util.regex.Pattern 可以覆盖equals,以检测两个Pattern实例是否代表同一个正则表达式,但是设计中并不认为客户需要或者期望这样的功能,在这类情况执行,从object继承得到equals 实现就已经足够了

  1. 超类已经覆盖了equals,超类的行为对于这个类也是合适的

例如大多数的Set 实现都从AbstractSet 继承equals实现,list实现从AbstractList继承equals实现,Map实现从AbstractMap继承equals实现

  1. 类是私有,或者是包级别私有的,可以确定它的equals方法永远不会被调用

如果你非常想要规避风险,可以覆盖equals方法,以确保不会被意外调用

@Override
public boolean equals(Object o){
	throw new AssertionError();
}

什么时候应该覆盖equals方法

如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖 equals 这通常属于“值类”的情形 ,值类仅仅是一个表示值的类,例如 Integer 或者 String,程序员在利用 equals 方法来比较值对象的引用时,希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象 为了满足程序员的要求,不仅必须覆盖 equals 方法,而且这样做也使得这个类的实例可以被用作映射表( map )的键( key ),或者集合( set )的元素,使映射或者集合表现出预期的行为

如何覆盖equals方法

  • 自反性

对于任何非null引用x ,x.equals(x) 必须返回true

  • 对称性

对于任何非 null 的引用值 x y,当且仅当 y.equals(x) 返true 时',x.equals(y )必须返回 true

  • 传递性

对于任何非 null 的引用值x y z ,如果 x.equals(y )返回true ,并且 y.equals(z )也返回 true ,那么 x.equals(z )也必须返回 true

  • 一致性

对于任何非 nu ll 引用值 x y ,只要 equals 的比较操作在对象中所用的信息没有被修改,多次调用 x.equals(y )就会一致地返回 true,或者一致地返回 false

  • 对于任何非null引用x ,x.equals(null) 必须返回false

最后的告诫

  • 覆盖equals时总要覆盖hashCode
  • 不要企图让equals方法过于智能
  • 不要将equals 声明中的object对象替换为其他的类型

11. 覆盖equals时总要覆盖hashCode

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

Object 规范:

  • 在应用程序的执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对同一个对象的多次调用, hashCode 方法都必须始终返回同一个值在。在一个应用程序与另一个程序的执行过程中,执行hashCode 方法所返回的值可以不一致
  • 如果两个对象根据 equals(Object )方法比较是相等的,那么调用这两个对象中hashCode 方法都必须产生同样的整数结果
  • 如果两个对象根据 equals(Object )方法比较是不相等的,那么调用这两个对象中的 hashCode 方法,则不一定要求 hashCode 方法必须产生不同的结果 但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表( hashtable )的性能

总而言之,每当覆盖 equals 方法时都必须覆盖 hashCode ,否则程序将无法正确运行hashCode 方法必须遵守 Object 规定的通用约定,并且必须完成一定的工作,将不相等的散列码分配给不相等的实例 这个很容易实现。

现在我们可以借用IDE来提供类似的功能

12. 始终覆盖toString

  1. 提供好的 String 实现可以便类用起来更加舒适,使用了这个类的系统也更易于调试
  2. 在实际应用中, toString 方法应该返回对象中包含的所有值得关注的信息
  3. 无论是否决定指定格式,都应该在文档中明确地表明你的意图
  4. 无论是否指定格式, 都为 toString 返回值中包含的所有信息提供一种可以通过编程访问之的途径
  5. Goog 公司开源的 Auto Value 工具,会替你生成 toString法,大多数集成开发环境 IDE 有这样的功能

13. 谨慎地覆盖clone

Cloneable 接口的目的是作为对象 一个 mixin( mixin interface ),表 明这样的对象允许克隆( clone )遗憾的是,它并没有成功地达到这个目的 它的
主要缺陷在于缺少一个 clone 方法, Object clone 法是受保护的

既然所有的问题都与 Cloneable 接口有关,新的接口就不应该扩展这个接口,新的可扩展的类也不应该实现这个接口 虽然 final 类实现 Cloneable 口没有太大的危害,这个应该被视同性能优化,留到少数必要的情况下才使用(详见第 67 条) 总之,复制功能最好由构造器或者工厂提供 这条规则最绝对的例外是数组,最好利用 clone 方法复数组

14. 考虑实现Comparable 接口

总而言之,每当实现一个对排序敏感的类时,都应该让这个类实现 Comparable口,以便其实例可以轻松地被分类搜索,以及用在基于比较的集合中 每当在 compareTo 方法的实现中比较域值时,都要避免使用 < 和>操作符,而应该在装箱基本类型的类中使用静态的 compare 方法,或者Comparator 接口中使用比较器构造方法