equals和hashcode改写

equals()

什么时候不需要重写

  • 一个类的每个实例本质上就是不可以相同的。例如Thread
  • 不需要关心一个类是否"逻辑相等"
  • 超类已经改写并且超类的行为对于子类来说也是合适的
  • 一个类的equals方法在程序运行时永远都不会被调用

什么时候需要重写

一个类需要有自己特有的"逻辑相等"概念,而且超类也没有改写equals来实现期望的行为或实现的行为不适用(例如Date与其子类Timestamp)

重写equals时的规范约定

  • 自反性:x.equals(x)为true
  • 对称性:x.equals(y)为true <=> y.equals(x)为true
 public class CaseInsensitiveString{
   private String s;

   public CaseInsensitiveString(String s)throws Exception{
     if(s == null){
       throw new NullPointerException();
     }
     this.s = s;
   }

   @override
   public boolean equals(Object o){
     if(o instanceof CaseInsensitiveString){
       return s.equalsIngnoreCase(((CaseInsensitiveString)o).s);
     }
     if(o instanceof String){
       return .equalsIngnoreCase((String)o);
     }
     return false;
   }
 }
  • 传递性: x.equals(y)为true, 且y.equals(z)为true <=> x.equals(z)为true
以一个二维点类为例, 若采用以下写法,则有色点作比较时会忽略颜色信息
  // 普通点类
  public class Point{
    private int x;
    private int y;
    
    public void Point(Object o){
       if(!(o instanceof Point)){
         return false 
       }
       Pint p = (Point)o;
       return p.x == x && p.y == y;
    }
  }
  // 有色点类
  public class ColorPoint extends Point{
    private Color color;

    public void ColorPoint(int x, int y, Color color){
      super(x, y);
      this.color = color;
    }
  }
 ```
 
因此需要在子类重写equals()方法,若采用以下重写方法, 则违反了 __对称性__
```Java
 public boolean equals(Object o){
   if(!(o instanceof ColorPoint)){
     return false;
   }
   ColorPoint cp = (ColorPoint)o;
   return super.equals(o) && cp.color == this.color;
 }

 Point p = new Point(1, 1);
 ColorPoint q = new ColorPoint(1, 1, red);
 p.equals(q); // true
 q.equals(p; // false

因此在子类采用一下改写方法, 但违背了 传递性

public boolean equals(Object o){
   if(!(o instance of Point)){
     return false
   }
   if(!(o instanceof ColorPoint)){
     return o.equals(this);
   }
   ColorPoint cp = (ColorPoint)o;
   return super.equals(o) && cp.color == this.color;
 }
 
 ColorPoint x = new ColorPoint(1, 1, red);
 Point y = new Point(1, 1);
 ColorPoint z = new ColorPoint(1, 1, blue);
 x.equals(y); // true
 Y.equals(Z); // true
 X.equals(z); // false

根据以上,要想在扩展一个可实例化类的同时,既要增加新的特征,同时还要保留equals约定,没有一个简单的办法可以做到这一点

因此根据建议: 复合优先于继承,类解决问题

public class ColorPoint{
  private Point point;
  private Color color;

  public ColorPoint(int x, int y, Color color){
    point = new Point(x, y);
    this.color = color;
  }

  public boolean equals(Object o){
    if(!(o instanceof ColorPoint)){
      return false;
    }
    ColorPoint cp = (ColorPoint)o;
    return cp.point.equals(point) && cp.color.euals(color);
  }
}
  • 一致性: 在程序中若x,y未发生变化,那么多次调用x.equals(y)返回结果是相同的

  • 非空性: x.equals(y)一定是false

// 在equals()中进行两个对象逻辑判断前,前确认比较的对象是否为空
 public boolean equals(Object){
   if(0 == null){
     return false;
   }
   .....
 }

如何正确改写equals()

根据以上特性,总结:

  1. 判断实参是否为空
  2. 使用==操作符检查“实参是否为指向对象的引用”
  3. 使用instanceof操作符检查“实参是否为正确的类型”
  4. 把实参转换到正确的类型
  5. 把该类中每一个 "关键"域,检查实参中的域与当前对象中对应的阈值是否匹配。
  6. 写完后,检查对称性、传递性、一致性
  7. 要重写hashCode()方法

hashCode()

什么时候重写

在每个改写了equals方法的类中也必须改写hashcode方法

原因:如果不这样做的话,会违反ObjectCode的通用约定,从而导致该类无法与所有基于散列值(hash)的集合结合在一起正常运作。

java.lang.Object规范中,hashcode的约定:

  • 在一次运行期间,同一个对象拥有通过一个散列码,对象equals()中用到的信息一旦被修改,则对象也改变。在多次运行时,一个对象的散列码可以是不同的
  • 相等的对象必须具有相等的散列码。如果两个对象根据equals()方法是相等的,那么两个对象中任意对象的hashCode()犯法必须参数同样的整数结果
  • 不等的对象可以有相同的散列码。但是对于不行等的对象产生截然不同的整数结果,有可能提高散列表的性能。

如何改写

一个好的散列函数通常倾向于“为不相等的对象产生不行等的散列码”

  1. 把某个非零常数值,比如17,保存在一个result的int型变量中

  2. 对于对象中没个“关键”域,完成以下步骤:
    a. 为该域计算int类型的散列码c:

    • 该域为boolean类型,在计算(f ? 0 : 1)
    • byte、char、short、int类型, 则计算(int)f
    • long类型,则(int)(f ^ (f >>> 32))
    • float类型,则Float.floatToIntBits(f)
    • double类型,则Double.doubleToLongBits(f)得到一个long类型得知,然后按照long类型步骤计算
    • 如果该域是对对象的引用,并且该类的equeals方法通过递归调用equals的方式类比较这个域,则同样对这个域递归调用hashCode。如果要求一个更为复杂的比较,则为这个与计算一个"规范表示",然后针对这个范式表示调用hashCode,如果阈值为null,则返回0
    • 如果改于十一个数字,则把数组中的每个元素当作一个单独的与来处理

    b. 把步骤a中计算的散列码c组合到result中:

    result = 37 * result + c;
    
  3. 返回result

  4. 该写完后,检查“是否相等的实例具有相等的散列码”,如果不是,找出原因并改正。

  5. 当一个类是可变的,并且计算散列码的代价也比较大,这是需要考虑把散列码缓存中对象内部

摘自: 《Effective Java》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值