第三章 对于所有对象都通用的方法

第8条: 覆盖equals时请遵守通用约定
  1. 以下情况,类的每个实例都只与它自身相等:
    • 类的每个实例本质上都是唯一的;
    • 不关心类是否提供了“逻辑相等”的测试功能;
    • 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的;
    • (如何理解?)类是私有的或者包级私有的,可以确定他的equals方法永远不会被调用;

在这种情况下,无疑是应该覆盖equals方法的,以防止他被意外调用:

 @Override public boolean equals(Object o) {
  throw new AssertionError(); // Method is never called
}
  1. “值类”需要覆盖equals的情形:值类即表示值的类,例如Integer,Date。覆盖equals方法以希望确定它们在逻辑上是否相等的情况。

  2. “值类”需要覆盖equals的情形:当值类为实例受控类,即每个值至多只存在一个对象的类时,不需要覆盖equals,直接调用Object.equals即可,因为此时逻辑相等与对象相等是同一个概念。

  3. equals方法实现了等价关系:

    • 自反性:对于任何一个非null的引用值x,x.equals(x)必须返回true;
    • 对称性:对于任何非null的引用值x和y,当且仅当y.equals(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的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会已知地返回true,或者一致地返回false。
    • 非空性:对于任何非null的引用值x,x.equals(null)必须返回false。

小结:保证高质量equals的诀窍

  1. 使用==操作符检查“参数是否为这个对象的引用”;
 public boolean equals(Object obj) {
   return (this == obj);
 }
  1. 使用instanceof操作符检查“参数是否为正确的类型”;
public boolean equals(Object o) {
  if(!(o instanceof ClassType)) 
    return false;
  ...
}
  1. 把参数转换成正确的类型;
public boolean equals(Object o) {
  if(!(o instanceof Type)) 
    return false;
  Type type = (Type)o;
  ...
}
  1. 对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配;
    1)对于既不是float也不是double类型的基本类型,可以使用==操作符进行比较;
    2)对于对象引用,可以递归的使用equals方法;
    3)对于float域可以使用Float.compare,对于double域可以使用Double.compare,因存在Float.NaN、-0.0f以及类似的double常量;
    4)对于集合类需要比对每一个元素,如Arrays.equals;
    5)避免合法null域,可采用以下代码;
    6)当你编写完成了equals方法之后,应该问自己三个问题:它是否对称、传递、一致?
    7) 覆盖equals时总要覆盖hashCode;
    8) 不要企图让equals过于智能;
    9) 不要将equals声明中的Object对象替换为其他类型;
//5)的代码案例
field == null ? o.field == null : field.equals(o.field);
第9条:覆盖equals时总要覆盖hashCode
  1. 基本解决方法:
    1) 把某个非零常数值,比如说17,保存在一个名为result的int类型的变量中;
    2) 对于对象中每个关键域f(指equals方法中涉及的 每个域),完成以下步骤:
    a. 为该域计算int类型的散列码c:
    i. 如果该域是boolean类型,则计算(f?1:0);
    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,为得到的long类型值计算散列值;
    vi.如果该域是一个对象引用,并且该类型的equals方法通过递归的调用equals的方式来比较这个域,则同样的为这个域递归的调用hashCode。如果需要更复杂的比较,则为这个域计算一个范式,然后针对这个范式调用hashCode。如果这个域值为null,则返回0(或者其他某个常数,但通常是0);
    vii.如果该域是一个数组,则要把每一个元素做单独的域来处理。也就是说,递归的应用上述规则,对每个重要的元素计算一个散列吗,然后根据步骤2.b中的做法把这些散列值组合起来。如果数组域中的每一个元素都很重要,可以利用发行版本1.5中增加的其中一个Arrays.hashCode方法;
    b. 按照下面的公式,把步骤2.a中计算得到的散列码合并到result中:result = 31 * result + c;
    3) 返回result;
public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
public PhoneNumber (int areCode, int prefix, int lineNumber) {
  rangeCheck(areaCode, 999, "area code");
  rangeCheck(prefix, 999, "prefix");
  rangeCheck(lineNumber, 9999, "line number");
  this.areaCode = areaCode;
  this.prefix = prefix;
  this.lineNumber = lineNumber;
}
private static void rangeCheck(int arg, int max, String name) {
  if(arg < 0 || arg > max) {
    throw new IllegalArgumentException(name + ":" + arg);
}
}
public boolean equals(Object o) {
...
}
public int hashCode() {
  int result = 17;
  result = 31 * result + areCode;
  result = 31 * result + prefix;
  result = 31 * result + lineNumber;
  return result;
}
}

NOTE:如果一个类是不可变的,并且计算散列码的开销比较大,应该采用散列码缓存对象内部而不是每次请求的时候重新计算散列码;

//Lazy initialized, cached hashCode
private volatile int hashCode;
public int hashCode() {
 int result = hashCode;
 if(result == 0){
    int result = 17;
     result = 31 * result + areCode;
     result = 31 * result + prefix;
     result = 31 * result + lineNumber;
     return result;
 }
return result;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容