读《effective java》学习笔记三

第六条 在改写equals的时候请遵守通用规定
  有一种“值类”可以不要求改写equals方法,类型安全枚举类型,因为类型安全枚举类型保证每一个值之多只存在一个对象,所有对于这样的类而言,Object的queals方法等同于逻辑意义上的equals方法。
 
  在改写equals方法的时候,要遵循的规范: 
        1,自反性。对任意的引用值x,x.equals(x)一定是true
        2,对称性。对于任意的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)也一定返回true.
        3,传递性。对于任意的引用值x,y和z,如果x.equals(y)==true and y.equals(z)==true,so x.equals(z)==true.
        4,一致性。对于任意的引用值x和y,如果用于equals比较的对象信息没有被修改的话,那么,多次调用x.equals(y)要么一致地返回true,要么一致地返回false.
        5,非空性。对于任意的非空引用值x,x.equals(null)一定返回false.
        
  自反性:要求一个对象必须等于其自身。一个例子:你把该类的实例加入到一个集合中,则该集合的contains方法
将果断地告诉你,该集合不包含你刚刚加入的实例. 

  对称性:
  例如:
     public final class CaseInsensitiveString{
           private String s;
           public CaseInsensitiveString(String s){
              if(s==null)   throw new NullPointerException();
              this.s=s;
           }
           public boolean equals(Object o){
              if(o instanceof CaseInsensitiveString)
                 return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
              if(o instanceof String)
                 return s.equalsIgnoreCase((String)o);
               return false;
            }
          }
调用:
   CaseInsensitiveString cis=new CaseInsensitiveString("Polish");
       String s="polish";
正如我们期望的:cis.equals(s)==true but s.equals(cis)==false
这就违反了对称性的原则.为了解决这个问题,只需把企图与String互操作的这段代码从equals方法中去掉旧可以了.这样做之后,你可以重构代码,使他变成一条返回语句:
public boolean equals(Object o){
   return o instanceof CaseInsensitiveString && ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
}

 

传递性---即如果一个对象等于第二个对象,并且第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。
例如:
       public class Point{
           private final int x;
           private final int y;
           public Point(int x,int y){
                this.x=x;
                this.y=y;
          }
          public boolean equals(Object o){
             if(!(o instanceof Point))
              return false;
             Point p=(Point)o;
             return p.x==x&&p.y==y;
          }
     }
现在我们来扩展这个类,为一个点增加颜色信息:
   public class ColorPoint extends Point{
      private Color color;
      public ColorPoint(int x,int y,Color color){
          super(x,y);
          this.color=color;
      }
   }
分析: equals方法会怎么样呢?如果你完全不提供equals方法,而是直接从Point继承过来,那么在equals座比较的时候颜色信息会被忽略掉。如果你编写一个equals方法,只有当实参是另一个有色点,并且具有同样的位置和颜色的时候,它才返回true:
       public boolean equals(Object o){
          if(!(o instanceof ColorPoint)) return false;
          ColorPoint cp=(ColorPoint)o;
          return super.equals(o) && cp.color==color;
       }
分析:这个方法的问题在于,我们在比较一个普通点和一个有色点,以及反过来的情形的时候,可能会得到不同的结果。前一种比较忽略了颜色信息,而后一种比较总是返回false,因为实参的类型不正确。例如:
     Point p=new Point(1,2);
     ColorPoint cp=new ColorPoint(1,2,Color.RED);
然后:p.equals(cp)==true but cp.equals(p)==false

修正:让ColorPoint.equals在进行“混合比较”的时候忽略颜色信息:
   public boolean equals(Object o){
       if(!(o instanceof Point)) return false;
       if(!(o instanceof ColorPoint)) return o.equals(this);
       ColorPoint cp=(ColorPoint)o;
       return super.equals(o) && cp.color==color;
   }
这种方法确实提供了对称性,但是却牺牲了传递性:
   ColorPoint p1=new ColorPoint(1,2,Color.RED);
   Point p2=new Point(1,2);
   ColorPoint p3=new ColorPoint(1,2,Color.BLUE);
此时:p1.equals(p2)==true and p2.equals(p3)==true but p1.equals(p3)==false很显然违反了传递性。前两个比较不考虑颜色信息,而第三个比较考虑了颜色信息。

结论:要想在扩展一个可实例华的类的同时,既要增加新的特征,同时还要保留equals约定,没有一个简单的办法可以做到这一点。根据"复合优于继承",这个问题还是有好的解决办法:我们不让ColorPoint扩展Point,而是在ColorPoint中加入一个私有的Point域,以及一个公有的视图方法,此方法返回一个与该有色 点在同一位置上的普通Point对象:
    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 Point asPoint(){
           return point;
       }
     
       public boolean equals(Object o){
           if(!(o instanceof ColorPoint)) return false;
           ColorPoint cp=(ColorPoint)o;
           return cp.point.equals.(point) && cp.color.equals(color);
       }
    }
   
注意,你可以在一个抽象类的子类中增加新的特征,而不会违反equals约定。

 

一致性:
 如果两个对象相等的话,那么它们必须始终保持相等,除非有一个对象被修改了。由于可变对象在不同的时候可以与不同的对象相等,而非可变对象不会这样,这个约定没有严格界定。
 


非空性:没什么好说的。

1,使用==操作符检查“实参是否为指向对象的一个应用”。如果是的话,则返回true。
       2,使用instanceof操作符检查“实参是否为正确的类型”。如果不是的话,则返回false。
       3,把实参转换到正确的类型。
       4,对于该类中每一个“关键(significant)”域,检查实参中的域与当前对象中对应的域值是否匹配
     if (!(this.field == null ? o.field == null : this.equals(o.field)))
     //或者写成 if(!(this.field == o.field || (this.field != null && this.field.equals(o.field)))) 对于this.field和o.field通常是相同的对象引用,会更快一些。
       return false;
     //比较下一个field
     //都比较完了
     return true;
     
5.最后还要确认以下事情
   5.1)改写equals的同时,总是(必须)要改写hashCode方法(见【第8条】),这是极容易被忽略的,有极为重要的
   5.2)不要企图让equals方法过于聪明
   5.3)不要使用不可靠的资源。如依赖网络连接
   5.4)不要将equals声明中的Object对象替换为其他类型。
         public boolean equals(MyClass) 这样的声明并不鲜见,往外使程序员数小时搞不清楚为什么不能正常工作
         原因是这里是重载(overload)而并不是改写(override)(或称为覆盖、重写)
         相当于给equals又增加了一个实参类型为MyClass的重载,而实参为Object的那个equals并没有被改写,依然还是从Object继承来的最初的那个equals,所总是看不到程序员想要的效果。因为类库或其他人写的代码都是调用实参为Object型的那个equals方法的(别人又如何在事前知晓你今天写的MyClass呢?)

posted on 2009-07-28 20:30 胡鹏 阅读(214) 评论(0)  编辑  收藏 所属分类: 读《effective java》笔记


只有注册用户登录后才能发表评论。


网站导航:
 

导航

<2009年7月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

统计

常用链接

留言簿(3)

随笔分类

随笔档案

agile

搜索

最新评论

阅读排行榜

评论排行榜