美文网首页
Effective Java(二) 对象守则

Effective Java(二) 对象守则

作者: 凌云_00 | 来源:发表于2017-12-18 17:57 被阅读0次

Tip-8 覆盖equals时请遵守通用约定

  • 覆盖equals方法看起来很简单,但是有许多覆盖方式会导致严重错误,最容易避免这类问题的办法就是不覆盖equals方法。

  • 不覆盖的理由

    • 每个对象都是唯一的,不需要覆盖
    • 不关心类是否提供了"逻辑相等"的函数或功能
    • 父类已经覆盖了equals,对子类同样适用
    • 类是私有的或包级别是私有的,可以确定它们的equals方法永远不会被调用
  • 覆盖的时机

    • 类具有特有的“逻辑相等”概念,类似String
    • 超累没有覆盖,子类需要equals实现特定的功能
    • 通常是类的成员变量的比值
  • 特殊的“值类“

    • 每个值只存在一个对象中,比如枚举类,对应这样的类,逻辑相同,与对象等同是一回事(所以枚举类常被用来做参数类型比较)
  • 覆盖equals需要遵守的规则,来自JavaSE6规范

    • 自反性 对于任何非null的引用值x,x.equals(x) 必须返回 true。
    • 对称性 对于任何非null的引用值x,y 当且仅当y.equals(x)= true 时,x.equals(y)也必须为true
    • 传递性 类似小学数学+-*/括号运算。。。
    • 一致性 在对象的值没有被修改的情况下 无论几次调用equals 返回的值 应一致
    • 非空性 对于任何非null对象,x.equals(null)必须返回 false;
  • 警告

    • 覆盖equals时总要覆盖hashCode
    • 不要企图让equals方法过于智能
    • 不要讲equals声明中的 Object 参数类型 替换为其他的类型
class Test{
    
    private short areaCode;
    private short prefix;
    private short lineNumber;
    
    public Test(){
    } 
    
    public Test(int areaCode,int prefix,int lineNumber){
        this.areaCode = (short) areaCode;
        ......同......
    } 
    
    public boolean equals(Object o){
    
     if(o == this){
          return true;
      }
    
      if(!(o instanceof Test)){
         return false;
       }
    
      ....业务逻辑....
    
    }

}

Tip-9 覆盖equals时总要覆盖hashCode

  • 规则:在覆盖equals方法的类中也必须覆盖hashCode方法,如果不遵守,就会违反hashCode通用约定,从而导致该类无法于所有基于Hash(散列)的集合一起正常运行,这样的集合包括HashMap,HashSet,Hashtable

  • 通用约定

    • 在应用程序的一次执行期间如果equals方法没有修改,那么同一对象多次调用hashCode方法的结果必须一致,在同一个应用程序多次执行过程中,hashCode返回的结果可以不一致
    • 如果两个对象在equals比较是相等的,那么两个对象的hashCode结果也必须一致(相同)
    • 如果两个对象在equals比较是不相等的,那么两个对象的hashCode结果不一定要不相同(可以相同),但是给不相等的对象产生截然不同的hashCode结果,可能提高散列表的性能
  • 失败例子


    // 使用第8 条的 Test 类
    Map<Test,Strign> map = new HashMap<Test,String>();   
    m.put(new Test(707,867,5309),"Lingyun");
    
    // str 实际内容 是null
    String str = m.get(new Test(707,867,5309));
    
- 这里实际涉及了两个Test对象,第一个被用于插入HashMap中,第二个用于获取str,但是由于 Test 没有覆盖HashCode方法,导致两个equals 或看似相等的对象实际拥有不同的Hash值,违反了HashCode约定   
  • 简单的HashCode覆盖方法
    1. 将一个非0的常数,比如17 赋值给 result 的int型变量中
    2. 对于对象中的每个关键域f,完成以下步骤
      • 计算该域的散列码
      • 按照公式 将计算出的散列码合并到result中 公式(result = 31 * result + c;)
    3. 返回result
    4. 写单元测试,验证是否equals相等的对象拥有相等的散列值,若不同修正错误

public int hashCode(){
    
    int result = 17;
    
    // 关键域
    result = 31 * result + areaCode;
    result = 31 * result + prefix;
    result = 31 * result + lineNumber;
    
    return reuslt;
    
}
  • 建议

    • 如果一个类是不可变的,并且计算散列值得开销较大,应该考虑把散列值缓存在对象内部(一个成员变量上),而不是每次请求时重新计算,
    • 如果一个类的大多数对象会计算散列值,就应该在创建对象时计算散列值,否则,可以选择“延迟初始化”散列值,一直到HashCode第一个调用时才初始化
  • 警告

    • 不要试图从散列值计算中排除一个对象的关键域来提高性能。虽然这样得到散列值速度加快,但是效果不一定好,可能会导致散列表慢到不可用

Tip-10 始终要覆盖toString

  • 提供好的toString实现可以使用类时感觉更加舒适。
  • toString 方法中应该返回对象中包含的所有值得关注的信息。

Tip-11 谨慎的覆盖clone

  • 最好别用,想要实现类似功能最好的办法是提供一个拷贝构造器或拷贝工厂

Tip-12 考虑实现Comparable接口

  • 建议

    • 可以进行等同比较,也可以进行顺序比较
    • 实现了Comparable接口,就表名类的对象具有内在的排序关系
    • 如果正在编写一个值类,并具有非常明显的内在排序关系,比如按字母顺序,按数值顺序或者按年代顺序,那么就应该考虑实现这个接口
  • 通用约定 sng 为signum函数,根据表达式的值为负值,0 ,正值分别返回-1 ,0 或1

    • 实现者必须确保所有x和y都满足 sng(x.compareTo(y)) == -sng(y.compareTo(x))
    • 实现者必须确保这个比较关系是可传递的 x>y y>z 暗示着 x>z
    • 实现者必须确保 x.compareTo(y)==0 暗示着 z 满足 sng(x.compareTo(z)) == sng(y.compateTo(z))
  • 警告

    • 不支持跨类比较
    • 就好像违反了hashCode约定的类使用HashMap等会受到影响一样,违反compareTo约定的类也会在使用TreeSet,TreeMap以及工具类Collections和Arrays受到影响,因为这些类的内部含有搜索和排序算法。
    • 有些技巧使用时要谨慎 比如 return x-y; 如果 x是一个特别大的正数,y是一个特别小的负数,返回的结果就会超过int最大值 ,导致程序崩溃,这些崩溃可能非常难以调试,因为这样的compareTo方法对于大多数的输入值都能正常工作

相关文章

网友评论

      本文标题:Effective Java(二) 对象守则

      本文链接:https://www.haomeiwen.com/subject/hoimwxtx.html