该不该覆盖equals?
覆盖equals是比较困难的,最容易避免的方式是不覆盖,在这种情况下类的每一个实例都与他自身相等。
满足下面的条件我们就可以不覆盖:
- 类的实体本质是唯一的 : 对于实体代表的不是值而是类。
- 根本不关心
equals
方法的相关类: - 超类已经覆盖了,而且超类覆盖的
equals
方法对子类也同样适用:如容器 - 类是私有的或者包级私有的保证他的
equals
方法永远不会被调用
那什么时候我们需要覆盖呢,如果类自身有特有逻辑相等的概念,而且超类并没有覆盖实现期望的行为,我们就需要覆盖equals
方法,这种类同时就是所谓的"值类" 如:Interger
或者Date
呀。 但是也有例外:实例受控(每个值至多指向一个对象)或者枚举类型的类。
equals覆盖的约定
- 自反性 : 自身 equals ture 自身,如果违反这一点,如我们添加实例到
Set
中的话,contians方法会判断不了是不是有实例在容器中。 - 对称性:
a.equals(b)==ture
则b.equals(a)==ture
,如一个不区分大小的String和区分大小写的String就违反了对称性,如果添加进容器,错误更是不明。 - 传递性 :
(a -> b == ture & & b -> c == ture) => a->c == ture
,比较难解决的点如果向一个无颜色的点 和有颜色的点比较(p33),会暴露java的基本问题:我们无法在可实例化的同时,即增加新组件,又同时保留equals的约定,但是有两个权衡之计 :
- 复合大于继承: 我们让子类不继承,而是持有私有父类的引用属性,子类也不在继承与父类,自然在
equals
的时候就不用考虑相关问题了 - 让可实例化变成不可实例化,如变为抽象类
- 一致性 : 只要类的所用的信息不修改 ,不论调用equals多少次,返回结果都是一样的。我们应该先确定对象是否可变,如果不可变,那么相等一辈子都相等。而且
equals
不要依赖于不可靠资源。 - 非空性:对于非null ,调用 a.equals(null),返回必定为空,但是我们一般都不用太关心,因为首先我们都会调用
instanceof
我们必须十分谨慎的修改,因为我们调用equals方法,都依赖于它们对象,所以如果发生相关的错误是很难发现。
写高质量eqauls的诀窍
使用
==
操作符检查参数是不是这个对象的引用使用
instanceof
检查参数是不是正确的类型把参数转为正确的类型,但在这之前得用
instanceof
检查一遍-
对于类的每一个关键的域,都要检查参数中的域是不是相同:如果类型是接口就调用接口的方法,如果是类就访问可访问类(不一定)
- 基本类型用 ‘==’
- 对于应用于用
equals
方法链 - 对于float或者double域 用
.compare
- 某些引用域为null是合法的为了避免空指针异常需注意
为了保证性能,最先比较的是最可能不一致的域,或者是开销最低的域
覆盖
equals
总要覆盖hashcode()
不要让
equals
过于智能,这样会增加工作量还得不偿失千万不要将
equals
声明的Object的对象替换成相关类型,这样他就成了重载,而不是覆盖,这会严重的增加复杂度。写完反问自己是不是对称,是不是一致,是不是传递