第7条:在改写equals的时候请遵守通用约定:
通用约定如下:
1、反身性(reflexive):对于任意 x,x.equals(x)必定传回 true。
2、对称性(symmetric):对于任意 x,y,当且仅当y.equals(x)传回 true,则x.equals(y)传回 true。
3、递移性(transitive):对于任意 x,y,z,如果 x.equals(y)传回 true,且y.equals(z)传回true,则x.equals(z)必定传回true。
4、一致性(consistent):对于任意 x,y,多次调用 x.equals(y)将始终如传回 true或 false — 前提是用于 equals()比较动作之对象信息不曾被改动。
5、对于任意 non-null reference x,x.equals(null)必定传回 false。
注意:
1、要想在扩展一个可实例化的类的同时,既要增加新的特性,同时还要保留equals约定,没有一个简单的方法可以做到这一点。
例如:java平台库中,有一些类是可实例化的累的子类,并且加入了新的特性。java.sql.Timstamp对java.util.Date进行了子类化,并且增加 nanoseconds 成员。Timestamp 的 equals()函式违反了对称性:
如果Timestamp 和 Date 对象被置于同个 collection ,或以其他方式被混用,会导致无法预测的奇怪行为。Timestamp class 有个声明,警告程序员切勿混用 Dates和 Timestamps。
2、可以在一个抽象类的子类中添加新特性,而不违反equals约束。(只要是不可能创建实例的超类,1中的情况就不会发生)。
按下面的方式就可以实现高质量的equals方法:
1、以 == 运算符检查「自变量是否为对象自身的引用」。如果是,传回 true。
2、以 instanceof 运算符检查自变量是否为正确型别。如果不是,就传回 false。
3、将自变量转换为正确型别。由于这个转换在 instanceof 测试之后发生。
4、对于该类张的每一个“关键”域,减产实参中的域与当前对象汇总对应的域值是否匹配。如果所有的测试都成功则返回true;否则返回false;
对于域的比较顺序应该是最先比较最可能不一致的域,或者比较开销最低的域。以提高性能。
5、当你完成 equals()的撰写是重新检查是否符合equals约束。
例子:
1、hashCode()总是应该和 equals()同被覆写
2、不要让equals方法过于聪明。(不要做太多事)
3、不要让equals方法归于倚赖不可靠资源。例如:java.net.URL 的 equals()倚赖URL 的主机 IP 地址进行比较。将个主机名转译为 IP 地址,可能需要进行网络存取,而我们无法保证在不同的时间点获得相同的结果。
4、不要在 equals()声明中的Object代替成其他类型的对象 。例如
public boolean equals(MyClass o) {
...
}
这样只是重载了equals方法而不是重写。
第8条:改写equals方法是总是改写hashCode()
相同的对象必须有相同的hashcode,不同的对象必须有不同的hashcode,所以改写了equals方法后必须改写hashCode方法。
重写高质量hashCode方法的步骤:
1、将个非 0 常数,例如 17,储存于 int result 变量。
2、对对象的每个有意义的字段 f(更确切说是被 equals()所考虑的每
个字段)进行如处理:
A. 对这个字段计算出型别为 int 的 hash 码 c:
i. 如果字段是个 boolean,计算(f ? 0 : 1)。
ii. 如果字段是个 byte,char,short 或 int,计算(int)f。
iii. 如果字段是个 long,计算(int)(f^(f >>> 32))。
iv. 如果字段是个 float,计算 Float.floatToIntBits(f)。
v. 如果字段是个 double,计算 Double.doubleToLongBits(f),然后将
计算结果按步骤2.A.iii处理。
vi. 如果字段是个 object reference,而且 class 的 equals()透过「递归调用
equals()」的方式来比较这字段,那么就同样也对该字段递归调用
hashCode()。如果需要更复杂的比较,请对该字段运算个标准表述
式(canonical representation),并对该标准表述式呼叫 hashCode()。
如果域值是null,就传回0(或其他常数;传回0是传统做法)。
B. 将步骤 A 计算出来的 hash 码 c 按列公式组合到变量 result : result = 37*result + c;
3、传回 result。
4. 完成 hashCode()之后,反躬自省:是否相等的实体具有相等的 hash 码? 如果不是,找出原因并修正问题。
注意:
1、步骤 1用到了个非 0初值。
2、步骤2.B的乘法使得hash值与字段顺序有关。以 37 为乘数乃因为它是个奇质数。如果采用偶数而且乘法满溢(overflowed),
信息将会佚失,因为乘以 2 相当于移位(shifting)操作。采用质数的好处在这里 并不是那么目了然,但传统以来都在此处使用质数。
生成hashCode实例代码如下:
第9条:总是要改写toString方法
提供一个好的toString实现,可以使一个类使用起来更加愉快,可以在该方法中返回所有令人感兴趣的信息。
例如:
通用约定如下:
1、反身性(reflexive):对于任意 x,x.equals(x)必定传回 true。
2、对称性(symmetric):对于任意 x,y,当且仅当y.equals(x)传回 true,则x.equals(y)传回 true。
3、递移性(transitive):对于任意 x,y,z,如果 x.equals(y)传回 true,且y.equals(z)传回true,则x.equals(z)必定传回true。
4、一致性(consistent):对于任意 x,y,多次调用 x.equals(y)将始终如传回 true或 false — 前提是用于 equals()比较动作之对象信息不曾被改动。
5、对于任意 non-null reference x,x.equals(null)必定传回 false。
注意:
1、要想在扩展一个可实例化的类的同时,既要增加新的特性,同时还要保留equals约定,没有一个简单的方法可以做到这一点。
例如:java平台库中,有一些类是可实例化的累的子类,并且加入了新的特性。java.sql.Timstamp对java.util.Date进行了子类化,并且增加 nanoseconds 成员。Timestamp 的 equals()函式违反了对称性:
如果Timestamp 和 Date 对象被置于同个 collection ,或以其他方式被混用,会导致无法预测的奇怪行为。Timestamp class 有个声明,警告程序员切勿混用 Dates和 Timestamps。
2、可以在一个抽象类的子类中添加新特性,而不违反equals约束。(只要是不可能创建实例的超类,1中的情况就不会发生)。
按下面的方式就可以实现高质量的equals方法:
1、以 == 运算符检查「自变量是否为对象自身的引用」。如果是,传回 true。
2、以 instanceof 运算符检查自变量是否为正确型别。如果不是,就传回 false。
3、将自变量转换为正确型别。由于这个转换在 instanceof 测试之后发生。
4、对于该类张的每一个“关键”域,减产实参中的域与当前对象汇总对应的域值是否匹配。如果所有的测试都成功则返回true;否则返回false;
对于域的比较顺序应该是最先比较最可能不一致的域,或者比较开销最低的域。以提高性能。
5、当你完成 equals()的撰写是重新检查是否符合equals约束。
例子:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.extension == extension &&
pn.exchange == exchange &&
pn.areaCode == areaCode;
}
注意:1、hashCode()总是应该和 equals()同被覆写
2、不要让equals方法过于聪明。(不要做太多事)
3、不要让equals方法归于倚赖不可靠资源。例如:java.net.URL 的 equals()倚赖URL 的主机 IP 地址进行比较。将个主机名转译为 IP 地址,可能需要进行网络存取,而我们无法保证在不同的时间点获得相同的结果。
4、不要在 equals()声明中的Object代替成其他类型的对象 。例如
public boolean equals(MyClass o) {
...
}
这样只是重载了equals方法而不是重写。
第8条:改写equals方法是总是改写hashCode()
相同的对象必须有相同的hashcode,不同的对象必须有不同的hashcode,所以改写了equals方法后必须改写hashCode方法。
重写高质量hashCode方法的步骤:
1、将个非 0 常数,例如 17,储存于 int result 变量。
2、对对象的每个有意义的字段 f(更确切说是被 equals()所考虑的每
个字段)进行如处理:
A. 对这个字段计算出型别为 int 的 hash 码 c:
i. 如果字段是个 boolean,计算(f ? 0 : 1)。
ii. 如果字段是个 byte,char,short 或 int,计算(int)f。
iii. 如果字段是个 long,计算(int)(f^(f >>> 32))。
iv. 如果字段是个 float,计算 Float.floatToIntBits(f)。
v. 如果字段是个 double,计算 Double.doubleToLongBits(f),然后将
计算结果按步骤2.A.iii处理。
vi. 如果字段是个 object reference,而且 class 的 equals()透过「递归调用
equals()」的方式来比较这字段,那么就同样也对该字段递归调用
hashCode()。如果需要更复杂的比较,请对该字段运算个标准表述
式(canonical representation),并对该标准表述式呼叫 hashCode()。
如果域值是null,就传回0(或其他常数;传回0是传统做法)。
B. 将步骤 A 计算出来的 hash 码 c 按列公式组合到变量 result : result = 37*result + c;
3、传回 result。
4. 完成 hashCode()之后,反躬自省:是否相等的实体具有相等的 hash 码? 如果不是,找出原因并修正问题。
注意:
1、步骤 1用到了个非 0初值。
2、步骤2.B的乘法使得hash值与字段顺序有关。以 37 为乘数乃因为它是个奇质数。如果采用偶数而且乘法满溢(overflowed),
信息将会佚失,因为乘以 2 相当于移位(shifting)操作。采用质数的好处在这里 并不是那么目了然,但传统以来都在此处使用质数。
生成hashCode实例代码如下:
public int hashCode() {
int result = 17;
result = 37*result + areaCode;
result = 37*result + exchange;
result = 37*result + extension;
return result;
}
如果延时生成hashCode的话:// 延时初始化, 缓存hashCode值
private volatile int hashCode = 0; // (See Item 48)
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 37*result + areaCode;
result = 37*result + exchange;
result = 37*result + extension;
hashCode = result;
}
return hashCode;
}
第9条:总是要改写toString方法
提供一个好的toString实现,可以使一个类使用起来更加愉快,可以在该方法中返回所有令人感兴趣的信息。
例如:
/**
* 本函式传回电话号码的「字符串表述形式」(string representation)。字符串包含
* 14 个字符,格式为:"(XXX) YYY-ZZZZ",其 XXX 表示 area code(区码),
* YYY 表示 extension(扩充码),ZZZZ 表示 exchange(交换码)。
* (每个大写字母代表个十进制数字。)
*
* 如果电话号码的个成分的任何个无法填满字段,
* 就由前导的 0 填补。假设 extension(扩充码)的值是 123,那么
* 字符串表述形式的最后 4 个字母将是 "0123"。
*
* 注意,在 area code(区码)之后的闭合括号和 exchange(交换码)的第个
* 数字之间有个空格,做为分隔符。
*/
public String toString() {
return "(" + toPaddedString(areaCode, 3) + ") " +
toPaddedString(exchange, 3) + "-" +
toPaddedString(extension, 4);
}
/**
* 将 int 转换为指定长度的字符串,必要时由前导 0 填补。
* 假设 i >= 0,1 <= length <= 10,
* 且 Integer.toString(i).length() <= length。
*/
private static String toPaddedString(int i, int length) {
String s = Integer.toString(i);
return ZEROS[length - s.length()] + s;
}
private static String[] ZEROS =
{"", "0", "00", "000", "0000", "00000",
"000000", "0000000", "00000000", "000000000"};