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

覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重。最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每个实例都只与它自身相等。

那么,什么时候应该覆盖Object.equals呢?如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖

equals方法。这通常属于“值类(value class)"的情形。值类仅仅是一个表示值的类,例如Integer或者Date。程序员在利用equals方法来比较值对象的引用时,希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象。

equals方法实现了等价关系(equivalence relation ) :

· 自反性(reflexive)。对于任何非null引用值x, x.equals(x)须返回true。

· 对称性(symmetric)。对于任何非null的引用值x和y,当且仅当y.equals(x]返回true时,x.equals(y)必须返回true。

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

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

· 对于任何非null的引用值x, x.equals(null)必须返回false。

我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。

虽然没有一种令人满意的办法可以既扩展不可实例化的类,又增加值组件,但还是有一种不错的权宜之计(workaround)。根据第16条的建议:复合优先于继承。

得出以下实现高质量equals方法的诀窍

  1. 使用==操作符检查“参数是否为这个对象的引用”。
  2. 使用instanceof操作符检查“参数是否为正确的类型”。
  3. 把参数转换成正确的类型。因为转换之前进行过instanceof测试,所以确保会成功。
  4. 对于该类中的每个‘’关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。

    对干既不是float也不是double类型的基本类型域,可以使用==操作符进行比较;对于对象引用域,可以递归地调用equals方法;对于float域,可以使用Float.compare方法;对于double域,则使用Double.compare。对Float和double域进行特殊的处理是有必要的,因为存在着Float..NaN、-0.0f以及类似的double常量;详细信息请参考Float.equals的文档。
  5. 当你编写完成了equals方法之后,应该问自己三个问题:它是否是对称的、传递的、一致的。

关于equals方法的警告:

  1. 覆盖equals时总要覆盖hashCode。
  2. 不要企图让equals方法过于智能。
  3. 不要将equals声明中的Object对象(参数)替换为其他的类型。

第9条:覆盖equals时总要覆盖hashCode

下面是约定的内容,摘自Object规范[Java SE 6]

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

Note:

HashMap有一项优化,可以将与每个项相关联的散列码缓存起来,如果散列码不匹配,也不必检验对象的等同性。

在散列码的计算过程中,可以把冗余域(redundant field)排除在外。换句话说,如果一个域的值可以根据参与计算的其他域值计算出来,则可以把这样的域排除在外。必须排除equals比较计算中没有用到的任何域。否则很有可能违反hashCode约定的第二条。

如果一个类是不可变的,并且计算散列码的开销也比较大,就应该考虑把散列码缓存在对象内部,而不是每次请求的时候都重新计算散列码。如果你觉得这种类型的大多数对象会被用做散列键(hash keys ), 就应该在创建实例的时候计算散列码。否则,可以选择“延迟初始化(lazily initialize )”散列码,一直到hashCode被第一次调用的时候才初始化(见第71条)。



第10条:始终要覆盖toString

如果指定了字符串的格式,好再提供一个相匹配的静态工厂或者构造器,以便程序员可以很容易地在对象和它的字符串表示法之间来回转换。Java平台类库中的许多值类都采用了这种做法,包括BigInteger、BigDecimal和绝大多数的基本类型包装类( boxed primitive class )。

无论你是否决定指定格式,都应该在文档中明确地表明你的意图。如果你要指定格式,则应该严格地这样去做。

第11条: 谨慎地覆盖clone

Cloneable接口的目的是作为对象的一个mixin接n口 (mixin interface)(见第18条),表明这样的对象允许克隆( clone)。遗憾的是,它并没有成功地达到这个目的。其主要的缺陷在于,它缺少一个clone方法,Object的clone方法是受保护的。如果不借助于反封(reflection)(见第53条),就不能仅仅因为一个对象实现了Cloneable,就可以调用clone方法。即使是反射调用也可能会失败,因为不能保证该对象一定具有可访问的clone方法。

既然Cloneable并没有包含任何方法,那么它到底有什么作用呢?它决定了Object中受保护的clone方法实现的行为: 如果一个类实现了Cloneable, Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedExcepion异常。这是接口的一种极端非典型的用法,也不值得仿效。通常情况下,实现接口是为了表明类可以为它的客户做些什么。然而,对于Cloneable接口,它改变了超类中受保护的方法的行为。

如果你扩展一个实现了Cloneable接口的类,那么你除了实现一个行为良好的clone方法外,没有别的选择。否则, 最好提供某些其他的途径来代替对象拷贝,或者干脆不提供这样的功能。例如,对于不可变类,支持对象拷贝并没有太大的意义,因为被拷贝的对象与原始对象并没有实质的不同。

另一个实现时象拷贝的好办法是提供一个拷贝构造器(copy constructor)或拷贝工厂(copy factory)。拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类,例如:

有些专家级的程序员干脆从来不去覆盖clone方法,也从来不去调用它,除非拷贝数组。你必须清楚一点,对于一个专门为了继承而设计的类,如果你未能提供行为良好的受保护的(protected )clone方法,它的子类就不可能实现Cloneable接口。

第12条: 考虑实现Comparable接口

如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按年代顺序,那你就应该坚决考虑实现这个接口:

将这个对象与指定的对象进行比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。

《Effective Java》第3章 对于所有对象都通用的方法的更多相关文章

  1. [Effective Java]第三章 对所有对象都通用的方法

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  2. [Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第八 ---- 九条

    这一章主要讲解Object类中的方法, Object类是所有类的父类,所以它的方法也称得上是所有对象都通用的方法 第八条 覆盖equals时需要遵守的约定 Object中的equals实现,就是直接对 ...

  3. [Java读书笔记] Effective Java(Third Edition) 第 3 章 对于所有对象都通用的方法

    第 10 条:覆盖equals时请遵守通用约定 在不覆盖equals方法下,类的每个实例都只与它自身相等. 类的每个实例本质上都是唯一的. 类不需要提供一个”逻辑相等(logical equality ...

  4. Effective Java读书笔记——第三章 对于全部对象都通用的方法

    第8条:覆盖equals时请遵守通用的约定 设计Object类的目的就是用来覆盖的,它全部的非final方法都是用来被覆盖的(equals.hashcode.clone.finalize)都有通用约定 ...

  5. 《Effective Java》第2章 对所有对象都通用的方法

    第10条:覆盖equals时,请遵守通用约定 1.使用==来比较两个对象的时候,比较的是两个对象在内存中的地址是否相同(两个引用指向的是否为同一个对象):Object中定义的equals方法也是这样比 ...

  6. [Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第十---十一条

    第十条 始终覆盖toString() toString的实现可以使类使用起来更加舒适,在执行println等方法时打印出定制信息. 一单实现了自己的toString,指定输出的固定格式,在方法的文档说 ...

  7. Effective Java:对于全部对象都通用的方法

    前言: 读这本书第1条规则的时候就感觉到这是一本非常好的书.可以把我们的Java功底提升一个档次,我还是比較推荐的.这里我主要就关于覆盖equals.hashCode和toString方法来做一个笔记 ...

  8. Java高效编程之二【对所有对象都通用的方法】

    对于所有对象都通用的方法,即Object类的所有非final方法(equals.hashCode.toString.clone和finalize)都有明确的通用约定,都是为了要被改写(override ...

  9. 《Effective Java》读书笔记 - 3.对于所有对象都通用的方法

    Chapter 3 Methods Common to All Objects Item 8: Obey the general contract when overriding equals 以下几 ...

随机推荐

  1. UVA - 11754 Code Feat (分块+中国剩余定理)

    对于一个正整数N,给出C组限制条件,每组限制条件为N%X[i]∈{Y1,Y2,Y3,...,Yk[i]},求满足条件的前S小的N. 这道题很容易想到用中国剩余定理,然后用求第k小集合的方法输出答案.但 ...

  2. UVALive - 4126 Password Suspects (AC自动机+状压dp)

    给你m个字符串,让你构造一个字符串,包含所有的m个子串,问有多少种构造方法.如果答案不超过42,则按字典序输出所有可行解. 由于m很小,所以可以考虑状压. 首先对全部m个子串构造出AC自动机,每个节点 ...

  3. Python3.7安装pyspider

    下面是Python3.7安装pyspider的方式,能安装成功但是后期有很多问题,所以不建议,请使用3.5版本的Python进行安装!!!由于要做爬虫工作,所以学习pyspider框架,下面介绍安装步 ...

  4. Laser

    Petya is the most responsible worker in the Research Institute. So he was asked to make a very impor ...

  5. 【LeetCode】003. Longest Substring Without Repeating Characters

    Given a string, find the length of the longest substring without repeating characters. Examples: Giv ...

  6. Oracle job procedure 存储过程定时任务(转自hoojo)

    Oracle job procedure 存储过程定时任务 oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务. 一.查询系统中的job,可以查询视图 --相关视图 ...

  7. Nginx配置负载均衡服务器

    最近想买一台二手电脑当Linux服务器,一直没有买,暂时用windows来搞. Nginx下载地址:http://nginx.org/download/nginx-1.2.6.zip Tomcat下载 ...

  8. python 集合和深浅copy

    #1数据类型的补充#2.集合set#3.深浅copy 补充:str --> bytes s.encode('gbk')bytes --> str s.decode('gbk') 1.数据类 ...

  9. openvswitch以及docker网络

    修改docker0的IP,教程写的是/etc/default/docker文件,但是那是过时的配置,真正的配置是在/etc/docker/daemon.json,格式是json的: { "r ...

  10. HTML(超文本标记语言)

    学习地址:https://developer.mozilla.org/zh-CN/docs/Web/Html