第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. ThinkPHP中的find和select的区别

    ThinkPHP作为PHP中应用广泛又好用的框架,能比较快速的开发MVC架构的管理系统,获得了大量的应用.但是在ThinkPHP中select()和find()方法有什么区别呢? 事实上find()返 ...

  2. 洛谷 P3048 [USACO12FEB]牛的IDCow IDs

    题目描述 Being a secret computer geek, Farmer John labels all of his cows with binary numbers. However, ...

  3. Nexus安装以及2,3比较

    解压缩之后, 进入nexus-3.6.2-01/bin文件夹中,执行: ./nexus start 如果使用root将会得到一个告警:Detected execution as "root& ...

  4. Dubbo实现RPC调用使用入门

    使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian.HTTP.RMI.Memcached.Redis.Thrift等等.由于Dubbo将这些协议的实现进行了封装了,无论是服务端( ...

  5. SQL 2008提供几种数据同步方式

    SQL 2008提供几种数据同步的方式如下. 1.日志传送(Log Shipping),定时将主数据库的日志备份,恢复到目标数据库. 2.数据库镜像(Database Mirror),原理同日志传送, ...

  6. SignalR推送服务在Android的实现 SignalA

    SignalA是老外写的用于实现.net端推送消息至安卓端的实现,支持版本为android 2.3或以上,由于我的版本最低是2.2,所以只有把源码下下来自己改,如果你觉得太多了可自己编译成jar引用, ...

  7. web基础 (四) JavaScript基础

    JavaScript 简史略过…… 一.Javascript结构   ECMAScript  ECMAScript 是一个重要的标准,但它并不是 JavaScript 唯一的部分,当然,也不是唯一被标 ...

  8. SpringBoot外部配置

    Spring Boot的配置文件 Spring Boot使用一个全局的配置文件application.properties或者application.yml(yaml语言的配置文件),放置在src/m ...

  9. 跨数据文件删除flashback database

    Oracle flashback database的使用有一些限制,其中最主要的是flashback database不支持跨数据文件删除闪回和不支持跨数据文件shrink闪回.对于已经删除的数据文件 ...

  10. 10-28SQLserver基础--数据库管理器(基础操作)

    C#基础--数据库(用来存储大量的数据) 操作数据库文件唯一途径 SQL server,结构化查询语言简称SQL. Analysis services:分析挖掘数据 Reporting service ...