effective java读书笔记四

本文详细介绍了如何正确地重写equals方法和hashCode方法,确保Java对象的比较和哈希值生成遵循基本的约定,这对于集合类如HashSet和HashMap的正确工作至关重要。
第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约束。

例子:
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"}; 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值