Effective Java 学习笔记 (六)

第八条      改写equals时总是要改写hashCode

每个改写了equals方法的类中,你必须也要改写hashCode方法。
hashCode约定的内容:
1.    在一个应用程序执行期间,如果一个对象的equals方法做比较所用到的信息没有被修改的话,则对该对象调用hashCode方法多次,它必须返回始终如一的同一个整数。
2.    如果两个对象根据equals(Object)方法是相等的,这两个对象所产生的hashCode也相等。
3.    如果两个对象根据equals(Object)方法是不相等的,hashCode最好不等。
 
生成hashCode的处方:
1. 把某个非零常数值,保存在一个叫result的int变量中。
2. 对对象中每一个关键域f (指equals方法中考虑的每一个域),完成:
a. 为该域计算int类型的散列码c:
i.      如果该域是boolean类型,则计算(f ? 0 : 1)。
ii.     byte、char、short or int类型,则计算(int)f。
iii.   long: 计算(int) ( f ^ (f >>> 32) )。
iv.   float: 计算Float.floatToIntBits(f)。
v.     double:计算 Double.doubleToIntBits(f)得到一个long类型的值,然后按步骤iii 对该long型进行散列计算。
vi.   对象引用:若该类的equals通过递归调用equals来比较这个域,则同样对这个域递归调用 hashCode。如果要求更复杂的比较,则为这个域计算一个“规范表示”,然后对范式表示调用hashCode。如果这个值为null,则返回0。
vii. 数组:吧每个元素当作单独的域来处理。然后按b中做法把散列值组合起来。
 
b. 把步骤a中计算得到的散列码c组合到result中:
result = 37*result + c (选用37是因为它是一个奇素数)
3. 返回result。
4. 写完hashCode检查是否相等的实例具有相等的散列码。
如果一个类是非可变的,并且计算散列码的代价也很大,则考虑把散列缓存在对象内部,而不是每次请求的时候都重新计算散列码。如果觉得此类的大多数对象会被用做散列键,那么应该在实例被创建的时候就计算散列码。否则,可选择“迟缓初始化”散列码,一直到hashCode被第一次调用的时候才初始化。
   
private volatile iint hashCode = 0 ;
   
    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;
    }
不要试图从散列码计算中排除掉一个对象的关键部分以提高性能。
 
 
第九条      :总是要改写toString
java.lang.Object提供的toString方法,一般返回类的名字@散列码的无符号十六进制表示。toString的约定建议所有的子类都改写这个方法。
提供一个好的toString 实现,可以使一个类用起来更加愉快。
在实际应用中,toString 方法应该返回对象中包含的所有令人感兴趣的信息。
实现toString的时候,必须决定是否在文档中制定返回值的格式。对于值类,推荐这样做。好处是可以被用做一种标准的、无二义性的、适合人阅读的对象表达形式。一个好的做法是同时提供一个相匹配的String构造函数或者静态工厂,这样可以很容易地在对象和它的字符串表示之间来回转换。
指定格式的不足:如果这类已经被广泛使用了,则一旦指定了格式,必须坚持这种格式。
无论是否决定指定格式,都应该在文档中明确地表明你的意图最好为 toString 返回值中包含的所有信息,提供一种变成访问途径。
 
第十条      :谨慎地改写clone
Cloneable接口的目的是作为对象的一个mixin 接口,表明这样的对象允许克隆。可它并没成功达到这个目的,因为Object的clone方法是被保护的,如果不借助于映像机制(reflection 35条),则不能仅仅因为一个对象实现了Cloneable就可以调用clone方法。即使映像调用也可能失败,因为不能保证该对象一定具有可访问的clone方法。
Cloneable决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,则Object的clone方法返回该对象的逐域拷贝,否则抛出CloneNotSupportedException异常。这种用法不值得仿效。
为了实现一种语言之前的机制:无须调用构造函数就可以创建一个对象。Clone方法需要遵守下面的约定:
创建和返回该对象的一个拷贝,这里的“拷贝”的精确含义取决于该对象的类。
x.clone() != x ; x.clone().getClass() == x.getClass() ; x.clone().equals(x) == true
但这些都不是绝对要求。
         如果改写了一个非final 类的clone 方法,则应该返回一个通过调用super.clone 二得到的对象。这种机制大致类似于自动的构造函数链,不过它不是强制要求的。
         对于实现了Cloneable的类,我们总期望它也提供一个功能适当的公有clone方法。但通常,除非该类的所有超类都提供了一个行为良好的clone实现,否则是不可能的。
         假设你的超类都提供了行为良好的clone方法,你从super.clone()中得到的对象可能会接近于最终要返回的对象,也可能相去甚远,这取决于类的本质。从超类的角度看,这个对象将是原始对象功能完整的克隆。在这个类中声明的域将等同于被克隆对象中相应的域值。如果每个域包含原语类型值或者非可变对象的引用,返回的对象可能是你需要的对象。然而,如果对象中包含的域引用了可变的对象,或者代表序列号活其他唯一ID值的域,或者代表对象创建时间的域,那么使用super.clone可能会导致灾难性后果。
         clone 方法是另一个构造函数;你必须确保它不会伤害到原始的对象,并且正确地建立起被克隆对象中的约束关系。
    public Object clone() throws CloneNotSupportedException{
       Stack result = (Stack) super .clone();
       result. elements = (Object[]) elements .clone(); // 递归地调用 clone
       return result;
    }
 
clone 结构与指向可变对象的 final 域的正常用法是不兼容的。 这里如果 elements final 的,则这种方案不能正常工作。除非在原始对象和克隆对象之间可以安全地共享此可变对象。
如果你在为一个散列表编写clone方法,它的内部数据是由一个散列桶数组组成,每一个散列都指向“健-值”对链表的第一个条目。为了实现这个类的clone,必须每个组成桶的链表单独地拷贝,深度拷贝:
           Entry deepCopy(){
           Entry result = new Entry( key , value , next );
           for (Entry p = result; p. next != null ;p=p. next )
              p. next = new Entry(p. next . key ,p. next . value ,p. next . next );
           return result;
       }
         克隆复杂对象的最后一个方法是,先调用super.clone,然后把结果对象中的所有域都设置到它们的空白状态,然后调用高层的方法来重新产生对象的状态,比如 HashTable中的put(key,value)的方法。这种做法运行起来没有直接操作对象和其克隆的内部状态的clone方法快。
         如同构造函数一样,clone方法不应该在构造过程中,调用新对象中任何非final方法。因此,上一段的put(key,value)要么是final的,要么是私有的。
         如果一个可扩展类改写了clone方法,那么改写版本的clone方法应包含 CloneNotSupportedException 异常。这样做可以使子类通过提供下面的 clone 方法,选择温和地放弃克隆能力:
    public final Object clone() throws CloneNotSupportedException{
       throw new CloneNotSupportedException();
    }
 
         另一个实现对象拷贝的好办法是提供一个拷贝构造函数。
         public Yum(Yum yum);
         public static Yum newInstance(Yum yum);
         它们不依赖于某一种很有风险的、语言之外的对象创建机制;它们不要求遵守尚未良好文档化的规范;它们不会与final域的正常使用发生冲突;它们不会要求客户捕获不必要的被检查异常;它们为客户提供了一个静态类型化的对象。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值