本文是《Effective Java》一书的整理笔记
1 equals 方法
1.1 以下几种情况不需要覆盖equals
类的每个实例本质上是唯一的
用
Object
提供的equals
的实现就已经足够了不关心类是否提供了 逻辑相等 的测试功能
- 超类提供的
equals
的行为对子类也适合 - 枚举类型,以及单例类
对枚举类型而言,对象相同和逻辑相同是一回事
1.2 需要覆盖equals的几种情况
- 类是私有的或者包级私有,可以确定
equals
不会被调用,这是应该覆盖equals
防止被意外调用
@Override
public boolean equals(Object o){
throw new AssertionError();
}
- 如果类具有特有的逻辑相同(不同于 对象等同)
程序员在利用
equals
方法来比较值对象时,希望知道它们在逻辑上是否相等,不想了解它们是否指向同一对象。
1.3 覆盖equals方法时的通用约定
自反性 (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
1.4 实现equals的方法
- 使用==操作符检查 参数是否为这个对象的引用
使用
instanceof
操作符检查 参数是否为正确类型所谓 正确类型 是指
equals
方法所在的那个类,有时指该类所实现的接口把参数转换成正确的类型
对该类中的每个值进行比较
- 对于非
float
、double
的基本类型可以使用==操作符进行比较 - 对于对象引用域,可以递归调用
equals
方法 - 对于
float
可以使用Float.compare
方法 - 对于
double
可以调用Double.compare
方法 - 对于
float
和double
的特殊值处理可以参考Float.equals
的文档 - 对于数组可以使用上述方式挨个比较,或者调用
Arrays.equals
方法 - 为了防止有些对象引用域因为包含
null
而导致NullPointerException
异常,通常采用以下方法比较
(field == o.field) || (field != null && field.equals(o.field))
- 对于非
优先比较最有可能不一致的域,提供性能
- 不要将
euqals
声明中的Object
对象替换为其他类型 - 添加
@Overide
注解
2 覆盖equals时总要覆盖hashCode
2.1 hashCode规范
- 在程序执行期间,只要
equals
所比较的信息没有被更改,对一个对象的多次调用,应当返回相当的散列码 - 两个对象的
equals
方法比较是相等的,应当返回相同的散列码 - 两个对象的
equals
方法比较是不相等的,不一定要返回不同的散列码,但是不同的散列码会提高散列表的性能
2.2 hashCode的计算方式
- 选一个非0常数值,例如25,保存在
int result
变量中 对于
equals
方法涉及的每个域,完成以下步骤为该域计算int类型的散列码c:
- 如果为
boolean
类型,则计算f ? 1 : 0
- 如果为
byte
、char
、short
或者int类型,则计算(int)f
- 如果为
long
类型,则计算(int)(f ^ (f >>> 32))
- 如果为
float
类型,则计算Float.floatToIntBits(f)
- 如果为
double
类型,则计算Double.doubleToLongBits(f)
,然后再按long
类型计算 - 如果为对象引用,并且该类的
equals
方法递归的调用了equals
方法来比较这个域,则同样为这个域递归的调用hashCode
;否则需要对这个对象计算一个”范式”,针对这个”范式”调用hashCode
。如果该对象为null
,则返回0 - 如果为数组,则把每个元素当成单独的域处理,或者使用
Arrays.hashCode
方法
- 如果为
按照下列公式,将上述每步得到的散列码c合并到result
result = 31 * result + c;
- 返回result
3 始终覆盖toString 方法
toString
的输出应该是一个简洁的、信息富并且易于阅读的形式- 一旦指定了
toString
的输出格式,就必须坚持这种格式,因为有的程序员会编写相应的代码来解析这种字符串表示法。
4 谨慎覆盖clone 方法
Cloneable
接口没有包含任何方法,唯一的作用就是实现该接口的对象调用clone()
方法不会抛出异常,并且返回该对象的逐域拷贝。
逐域拷贝意思就是创建新的对象,并且逐个拷贝原对象的所有成员变量的引用。虽然新拷贝对象内的变量与原对象内的变量不是同一变量,但他们指向相同的实例。实际上就是浅拷贝。
Cloneable
接口改变了clone
方法的行为,是接口的一种极端的用法,不值得效仿。
使用clone
方法创建的对象,没有调用构造器
4.1 实现良好的clone行为
- 使用公有的方法覆盖
clone
- 首先调用
super.clone
,获对象的拷贝 - 如果该类包含需要修正的域,一般采用递归调用
clone
实现
需要修正的域,意思是除了基本类型以及不可变类型之外的成员变量。代表序列化,或者唯一ID值的域,或者代表对象的创建时间的域,一般也需要修正
public class HashTable{
private Object[] buckets
@Override public HashTable clone() {
try{
HashTable result = (HashTable)super.clone();
result.buckets = buckets.clone;
return result;
}catch(CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
在
clone
方法内不要调用新对象中的任何非final
方法如果
clone
调用了一个被覆盖的方法,那么在该方法所在的子类有机会修正它在克隆对象中的状态之前,该方法就会先被执行,这样很有可能会导致克隆对象和原始对象之间的不一致。在线程安全的类中实现
clone
方法,记得要做好同步。
4.2 clone的替代方式
使用clone
方法克隆对象有着种种限制,最好使用构造器或者构造工程克隆对象。
public Yum(Yum yum);
public static Yum newInstance(Yum yum);
5 考虑实现Comparable接口
如果你编写一个值类,并且具有明显的内在排序关系,例如字母顺序,数值顺序,那么就该考虑实现Comparable
接口
5.1 compareTo方法的通用约定
sgn会根据传入的值为负值、零值、正值,分别返回-1、0、1
- 将这个对象与指定对象进行比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零值、正整数。
- 如果指定对象类型无法与该对象进行比较,则抛出
ClassCastException
异常 - 必须满足
sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
,也就是说仅当x.compareTo(y)
抛出异常时,y.compareTo(x)
才抛出异常 - 比较关系是可传递的
(x.compareTo(y)>0 && y.compareTo(z)>0
时,意味着x.compareTo(z)>0
- 强烈建议
(x.compareTo(y)==0)==(x.equals(y))
,虽然并非绝对必要,但如果违反该条件应当予以说明:“注意,该类具有内在的排序功能,但与equals不一致
欢迎大家访问我的博客,转载请注明出处
http://blog.youkuaiyun.com/abyss521