Object类的所有非final方法(equals、hashCode、toString、clone、finalize
)都要遵守通用约定(general contract
),否则其它依赖于这些约定的类(HashMap,HashSet等
)将不能正常工作。
8、覆盖equals时请遵守通用约定
无需覆盖equals
的情形:
- 类的每个实例本质上是唯一的。类代表的是活动实体而不是值的概念。(例如,类
Thread
) - 不关心类“逻辑相等”的功能,从
Object
继承的equals
实现已经足够。(例如,类Random
) - 超类的
equals
实现也适用于子类。(例如,List
从AbstractList
继承equals
实现) - 类是private或包级私有(默认)的,可以确定它的
equals
方法永远不会被调用。 - “每个值最多有一个实例”的类,逻辑相同与对象相同一样。(例如,枚举类,Singleton类)
需要覆盖equals
的情形:
- 类具有“逻辑相等”的概念,而且超类没有覆盖
equals
方法。
覆盖equals
时必须遵守的约定:
- 自反性,
x
非空时,x.equals(x)
返回true
- 对称性,
x, y
非空时,若x.equals(y)
返回true
,则y.equals(x)
返回true
- 传递性,
x, y, z
非空时,若x.equals(y)
返回true
且y.equals(z)
返回true
,则x.equals(z)
返回true
- 一致性,
x, y
非空时,多次调用x.equals(y)
返回值一致 -
x
非空时,x.equals(null)
一定返回false
违反约定的例子:
在java类库中,java.sql.Timestamp
继承自java.util.Date
,并增加了nanoseconds
域。但Timestamp
的equals
实现违反了对称性(date.equals(timestamp) != timestamp.equals(date))
,如果Timestamp
和Date
对象被混合在一起使用,将引起不正确的行为。
equals
实现代码:
//Date中equals实现
public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
//Timestamp中equals实现
/*
* Note: This method is not symmetric with respect to the
* equals(Object) method in the base class.
*/
public boolean equals(java.lang.Object ts) {
if (ts instanceof Timestamp) {
return this.equals((Timestamp)ts);
} else {
return false;
}
}
注:在equals实现中,对于obj instanceof Date
语句,若obj为null,其将返回false。因此把null传个equals方法,无需进行单独的类型检查(判断obj是否为null)。
若将上面Timestamp
的equals
代码改为如下形式:
//Timestamp改进的equals实现
public boolean equals(java.lang.Object ts) {
if (ts instanceof Timestamp) {
return this.equals((Timestamp)ts);
} else if (ts instanceof Date){
return ((Date)ts).equals(this);
} else {
return false;
}
}
这样确实保证了对称性,但却牺牲了Timestamp
类的特征。
Timestamp是Date 类的瘦包装器 (thin wrapper),它允许 JDBC API 将该类标识为 SQL TIMESTAMP 值。它添加保存 SQL TIMESTAMP 毫微秒值和提供支持时间戳值的 JDBC 转义语法的格式化和解析操作的能力。
实现equals方法的诀窍:
使用 == 检查“参数是否为这个对象的引用”,特别是比较操作比较昂贵时。
使用instanceof操作符检查“参数是否为正确的类型”,若不是返回false
检查参数中的域是否与对象中的对应域相匹配
编写完equals方法后检查对称性、传递性、一致性
注意:
覆盖equals时总要覆盖hashCode
不要将equals中的Object替换为其他类型,使用
@Override public boolean equals(Object o)
,参数类型为Object,否则将重载equals方法
9、覆盖equals时总要覆盖hashCode
每个覆盖了equals方法的类中,必须覆盖hashCode方法,否则该类无法用于基于散列表的集合(HashMap,HashSet和HashTable)
对hashCode的约定:
对同一个对象调用多次(用于比较操作的信息未被修改),hashCode返回同一个整数
equals比较相等,则hashCode返回的值必须相同
equals比较不相等,hashCode返回的值可能相同,也可能不同
相等的对象必须具有相等的hashCode值;hashCode值不同,对象一定不相等,为不相等的对象产生不相等的散列码可以提高散列表的性能。散列函数应该把不相等的实例均匀分配到所有可能的散列值上。
一种计算散列码的方式:
1、保存一个非零的常数值,result = 17
2、为对象中每个域f(equals方法中涉及的域)计算int型的散列码c
- 域为boolean类型,
c = f ? 1 : 0
- 域为byte,char,short或int类型,
c = (int) f
- 域为long类型,
c = (int)(f^(f >>> 32))
- 域为float类型,
c = Float.floatToIntBits(f)
- 域为double类型,
f = Double.doubleToLongBits(f); c = (int)(f^(f >>> 32))
- 域为一个对象的引用,
c = f.hashCode()
- 域为数组,利用此方法递归计算数组的hashCode值
3、将所有的散列码合并到result,递归调用result = result * 31 + c
例如:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + fa;
result = 31 * result + fb;
result = 31 * result + fc;
return result;
}
使用31的原因:
31是一个奇素数并且31可以使用移位和减法来代替乘法以提高性能,如:31 * i == (i << 5) - i
。现代的JVM能够自动完成这种优化
对于不可变类,若每次计算hashCode的开销比较大,可将散列码缓存在对象内部,而不是每次请求时都重新计算散列码。如:
private volatile int hashCode = 0; //volatile
@Override
public int hashCode() {
if (hashCode != 0) {
return hashCode;
}
int result = 17;
result = 31 * result + fa;
result = 31 * result + fb;
result = 31 * result + fc;
hashCode = result;
return result;
}
10、始终要覆盖toString
toString的约定,建议所有的类都实现toString方法。toString实现可以使类用起来更加舒服,有利于调试时信息的诊断。
如果对象太大或信息难以用字符串描述,应该返回一个摘要信息。在文档中标明toString的返回格式。
11、谨慎的覆盖clone
一个类实现了Cloneable接口,表名这个类允许被克隆。Cloneable接口是个空接口,仅仅用来标识这个类可以被复制,具体实现将由JVM调用Object中的原生方法clone()完成。所以一个类要能够复制必须实现Cloneable接口并重写clone()方法。
关于clone方法的实现参见博客java中clone方法的实现
12、考虑实现Comparable接口
实现了Comparable接口的类,表明它的实例具有内在的排序关系,可以使用Arrays.sort(comp)
方法对其数组进行排序。一旦类实现了Comparable接口,它就可以与许多泛型算法及依赖于该接口的集合实现进行协作。若编写的类是有非常明显的内在排序关系,那么就应该实现Comparable接口。实现Comparable接口必须重写compareTo方法。
依赖于比较关系(compareTo方法)的类包括有序集合类TreeSet和TreeMap,以及工具类Collection和Arrays,它们内部包含有搜索和排序算法。
compareTo方法的约定:
- 该对象小于、等于、大于指定对象时,分别返回负整数、零、正整数。若两对象不同,则抛出
ClassCastException
异常 -
x.compareTo(y) == 0
与x.equals(y)
建议保持一致。