8.覆盖equals通用约定
概述
Object的设计是为了扩展,其所有的非
final
方法都有明确的通用约定,复写时需要遵循通用约定,以保证和其他类能协调使用.
不复写 equals
(即直接用Object#equals
)
- 类的每个实例本质上都是唯一的(针对代表活动实体而不是值的类)
- 不关心是否提供了逻辑相等的功能
- 超类覆盖的
equals
,通过继承过来的行为对子类也是合适的. - 类是
私有
或包级私有
的,可以确定其equals
永远不会被调用,可通过如下方式强化约束.
@Override public boolean equals(Object o) {
throw new AssertionError();
}
当类具有自己特有的逻辑相等
概念时,就需要重写equals
方法.
单例类的逻辑相同
和对象等同
是一回事.
equals通用约定
- 自反性
对于非null
的引用值x,x.equals(x)
必须返回true
- 对称性
对于非null
的引用值x,y
,当且仅当y.equal(x)==true
时,x.equals(y)==true
- 传递性
对于任何非null
的引用值x,y,z
,如果x.equals(y)== true
且y.equals(z)==true
,则x.equals(z) == true
.在扩展子类的时候尤其需要注意. - 一致性
对于非null
的引用值x,y
,只要equals
比较操作在对象中所用的信息没有被修改,则多次调用equals
方法返回值也应一致. - 非空性
对于任何非null
的引用值x
,x.equals(null) == false
告诫
- 复写
equals
时总要复写hashcode
- 不要企图让
equals
过于只能(即考虑所有值域情况) - 不要将声明中的
Object
对象替换为其他对象.
9.覆盖equals总要覆盖hashcode
概述
复写
equals
的类也必须复写hashcode
,否则无法结合基于散列
的集合一起正常工作.
通用约定
- 只要对象
equals
所用到的信息没有被修改.对于同一对象多次调用hashcode
必须返回同一个整数 - 如果俩个对象的
equals
比较相等.则两个对象的hashcode
产生同样的整数结果. - 如果
equals
不相等,则两个对象的hashcode
不一定产生不同的整数结果.
建议与须知
- 如果
hashcode
返回一个常量
,则每个对象具有相同的散列吗,,每个对象被映射到同一个散列桶
中,散列桶
会退化为散列表
结构. - 必须排除
equals
计算中没有用到的任何域. - 不要试图从
散列码计算
中,除掉一个对象的关键部分来提高性能. - 31 有个很好的特性,即
31*i == (i<<5)-i
,现代的VM
可以自动完成这种优化. - 如果一个类是不可变的,且计算
hashcode
开销比较大,建议将散列表缓存
在对象内部.
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
10.始终覆盖toString
- 提供好的
toString
使类用起来更加舒服 toString
应该包含所有值得关注的信息.- 无论是否指定格式,都应该为
toString
返回值中包含的所有信息,提供一种编程式的访问路径.
11.谨慎覆盖clone
概述
如果一个类实现了
Cloneable
,Object
的clone()
方法就会返回该对象的逐级拷贝,否则抛出异常.
clone 通用协议
对于任何对象 x
:
x.clone() != x
x.clone().getClass() == x.getClass()
,但这不是绝对要求x.clone().equals(x)
,也不是一个绝对要求
提醒:
- 如果覆盖了 非
final
类中的clone
方法,则应该返回一个通过调用super.clone
而得到的对象.
@Override protected Object clone() {
try {
return (PhoneNumber)super.clone();//不要让客户做转换
} catch (CloneNotSupportedException _e) {
throw new AssertionError();
}
}
- 如果对象中的域引用了可变的对象.仅仅
super.clone
,新clone
的对象的引用域将引用原来的可变对象.破坏了对象克隆的约束条件.
@Override public Stack clone() {//将protect--> public
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();//不伤害原始对象
return result;
} catch (CloneNotSupportedException _e) {
throw new AssertionError();
}
}
- 如果
elements是final的
将会导致失败,因为是被禁止给fianl
域重新赋值 - 要实现
clone
方法的类,都应该实现Cloneable
接口,同时把clone
方法可见性设为public
- 同构造器一样,
clone
方法不能在构造过程中调用新对象的非final
方法,防止子类修改其状态.
小结
- 所有实现了
Cloneable
接口的类,都应该用一个公有的方法 覆盖 clone
- 此方法首先调用
super.clone
- 最好提供其他的途径来代替对象拷贝,或者干脆不提供这样的功能.
- 另一种实现对象拷贝的方法是提供一个
拷贝构造函数
,或者拷贝工厂方法
,而且此种方法更加推荐. - 设计用来被继承的类时,如果不实现一个正确高效的
clone
重写,其子类也将无法实现正确高效的clone
功能
12.考虑使用Comparable接口
概述
当需要对象自然有序时,实现
Comparable
接口
通用协议
sgn(x.compareTo(y)) == - sgn(y.compareTo(x));
当类型不对时,应该抛出ClassCastException
x.compareTo(y) > 0 && y.compareTo(z) > 0
,则x.compareTo(z) > 0
x.compareTo(y) == 0
,则sgn(x.compareTo(z)) == sgn(y.compareTo(z))
- 建议与equals保持一致,但非必须.即
x.compareTo(y) == 0
, 则x.equals(y)
.
依赖于比较关系的类
TreeSet
,TreeMap
,Arrays
等,而HashSet, HashMap
则是通过equals + hashCode
保存
实现细节
- 从最关键的域开始,逐步进行到所有的重要域.
- 谨慎使用返回差值的方式,有可能会溢出(最大值和最小值之差
小于等于Integer.MAX_VALUE
)
示例 :PhoneNumber.java
关于equals()与hashCode()的总结
equals()方法
实践表明,当equals()方法被override时,hashCode()也要被override。按照一般hashCode()方法的实现来说,相等的对象,它们的hash code一定相等。
hashcode()方法
实践表明,由Object类定义的hashCode()方法对于不同的对象返回不同的integer。
hashcode与Java集合
- 当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。
- 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;
- 如果这个位置上已经有元素了(哈希冲突),就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。
1、equals相同,则hashcode一定相同。
2、equals不同,hashcode可能相同。
3、hashCode相同,equals可能相同。
4、hashcode不同,equals一定不同。
扩展阅读: