第8条:覆盖equals时请遵守通用约定

覆盖equals方法看似很简单,但是有许多覆盖方法或导致错误,避免这些错误最直接的方法就是不覆盖equals。
至于什么时候不覆盖equals方法,主要有下面三种:
   1:类的每个实例本质上是唯一的。
      对于代码活动实体而不是值的类,如Thread,Object提供的equals实现就是这些类的行为
   2:不关心类是否提供了“逻辑相等”的测试功能
      如Random类提供了随机数产生的能力,Object继承过来的equals已经足够了
   3:超类已经覆盖了equals,从超类继承过来的行为对于子类也是适合的
      如Set实现都是从AbstractSet继承equals实现,List实现从AbstractList继承equals实现,Map实现从AbstractMap继承equals实现。
   4:类是私有的或者包级私有,可以确定它的equals方法用于不会被调用。不过为了以防被意外调用,最好还是覆盖下equals
       @Override
       public boolean equals(Object o){
         throw new AssertionError();
       }

 

那什么时候应该覆盖equals方法呢?
  如果类具有自己特有的“逻辑相等”(不同意对象同等),而且超类没有覆盖equals来实现期望的行为,这时候就需要覆盖equals方法了。通常这种类是“值类”,仅仅表示值的类,如Integer,Date,在利用equals方法时比较对象引用时,希望知道它们在逻辑上是否相等(值是否相等),而不是它们是否指向同一个对象。这不仅必须覆盖equals方法,还可以让这个类的实例可以被用作映射表map中的key值或set中的元素。一种特殊的“值类”,实例受控确保“每个值至多只存在一个对象”的类,如枚举类型,对应这样的类,逻辑等同域对象等同是同样的,因此Object提供的equals以满足,就无需覆盖。

 

在覆盖equals时,必须遵守它的通用约定,约定内容如下:
  1:自反性,对于任何非null的引用值x,x.equals(x)必须返回true
  2:对称性,对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
  3:传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也必须返回true。
  4:一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用信息没有被修改,那么多次调用x.equals(y)就会一致地返回true或一致地返回false。
     对于任何非null的引用值x,x.equals(null)必须返回false。

 

实现高质量equals方法的诀窍:
  1:用==操作符来检查“参数是否是这个对象的引用”。如果是,则返回true。这样做是为了提高性能。
  2:使用instanceof操作符来检查“参数是否为真确类型”。如果不是,则返回false。“正确类型”一般指的是equals方法所在的类。有些情况是指该类所实现的某个接口。
  3:把参数转化为真确的类型。在被instanceof检测过后,我们就将Object强行转换成上面所提到的“正确的类型”,instanceof保证了我们转换的真确性。
  4:对于该类中的每个关键域(字段),检查参数中的域是否与该对象中所对应的域相匹配。强转之后,就可以开始匹配两个对象中的字段了。如果匹配成功就可以返回true,
     如果“正确的类型”是一个接口,那么需要接口提供方法来访问这些字段,如果是个类的话,那就不用说了。字段匹配技巧:
      a、如果是非float和double类型的基本数据类型,那么直接使用==符号。
      b、如果是float和double,则使用Float.compare和Double.compare方法。
      c、其他类型(也就是那些需要new出对象的类)则调用他们自身的equals方法。有些字段可能被允许为空,所以要进行判断,如下:
        field == null ? o.field == null : filed.equal(0.field);
      d、数组的话需要遍历每一个元素进行匹配,匹配的时候参考上面的三条方法。

//一个简单例子
@Override
public boolean equals(Object obj){
        if(obj == this){//引用是否是指向同一个Person对象
            return true;
        }else if(obj != null&&obj instanceof Person){//类型不为空,且为Person类型或子类。
            Person p = (Person) obj;
            if(this.name == null?this.name==p.getName():this.name.equals(p.getName())){
                if(this.age==p.getAge() && this.sex==p.getSex()){
                    return true;
                }
            }
        }
        return false;
}

@Override
public int hashCode(){
  ......
}

覆盖equals方法的时候我们还需要注意的地方:
  1:覆盖equals的时候总是要覆盖hashCode方法。(为什么覆盖和怎么覆盖会在下一篇文章讲)
  2:不要企图让equals方法过于智能。只是匹配对象的类型和对象中的各个参数的话很容易做到,并且一般不会违反上面提到的规范。如果你过度的去寻求各种等价关系,那么上面的约定将很难遵守。
  3:覆盖equals方法的时候请在方法前面加@Override注解。@Override注解可以防止本想覆盖而错写成重载的方法,如果你的目的是覆盖,就使用该注解,这样在你出错的时候,能提示你你写的方法并不是一个覆盖的方法。

 

posted @ 2017-04-18 11:27  哀&RT  阅读(431)  评论(0编辑  收藏  举报