• Item 8 覆盖equals时请遵守通用约定


    在覆盖equals方法的时候,你必须要遵守它的通用约定,不遵守,写出来的方法,会出现逻辑错误。下面是约定的内容:

     
    equals方法实现了等价关系:
     
    • 自反性。对于任何非null的引用值,x.equals(x)必须返回true。
    • 对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
    • 传递性。对于任何非null的引用值x,y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
    • 一致性。对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false。
    • 对于任何非null的引用值x,x.equals(null)必须返回false。

    对称性

    public final class CaseInsensitiveString {
      private final String s ;
    
      public CaseInsensitiveString(String s) {
        if (s == null)
          throw new NullPointerException();
        this. s = s;
      }
    
      // Broken - violates symmetry!
      @Override
      public boolean equals(Object o) {
        if (o instanceof CaseInsensitiveString)
          return s.equalsIgnoreCase(((CaseInsensitiveString) o). s);
        if (o instanceof String) // One-way interoperability!
          return s.equalsIgnoreCase((String) o);
        return false;
      }
    
      // This version is correct.
      // @Override public boolean equals(Object o) {
      // return o instanceof CaseInsensitiveString &&
      // ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
      // }
    
      public static void main(String[] args) {
        CaseInsensitiveString cis = new CaseInsensitiveString("Polish" );
        String s = "polish";
        System.out.println(cis.equals(s) + "  " + s.equals(cis));
      }
    }
    
    -------------------------
    true  false
     
    上述代码的问题,要通过写测试来发现。写测试的时候,按照对称性规则来写。对称性规则就是  x.equals(y)为true, y.equals(x)也要为true。
    如果,没有按照规则来写单元测试,那么,是不容易发现上述代码存在的问题的。关键就是,按照规则,实现了代码之后,要写单元测试。
     
    传递性
     
    public class ColorPoint extends Point {
        private final Color color ;
    
        public ColorPoint( int x, int y, Color color) {
            super(x, y);
            this.color = color;
        }
    
        // Broken - violates symmetry!
        @Override
        public boolean equals(Object o) {
            if (!(o instanceof ColorPoint))
                return false ;
            return super .equals(o) && ((ColorPoint) o).color == color;
        }
    
        // Broken - violates transitivity!
        // @Override public boolean equals(Object o) {
        // if (!(o instanceof Point))
        // return false;
        //
        // // If o is a normal Point, do a color-blind comparison
        // if (!(o instanceof ColorPoint))
        // return o.equals(this);
        //
        // // o is a ColorPoint; do a full comparison
        // return super.equals(o) && ((ColorPoint)o).color == color;
        // }
    
        public static void main(String[] args) {
            // First equals function violates symmetry
            Point p = new Point(1, 2);
            ColorPoint cp = new ColorPoint(1, 2, Color.RED);
            System. out.println(p.equals(cp) + " " + cp.equals(p));
    
            // Second equals function violates transitivity
            ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
            Point p2 = new Point(1, 2);
            ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
            System. out.printf("%s %s %s%n" , p1.equals(p2), p2.equals(p3),
                    p1.equals(p3));
        }
    }
    
    -----------------------------------------------------
    结果:
    true false
    false true false
     
    同理,使用传递性规则,写个测试,就可以发现问题。
     
    解决办法:
    使用复合,避免使用继承。也就是,在实现ColorPoint时,不继承Point,而在ColorPoint中放一个Point类型的成员。然后,所有ColorPoint都有Point类成员,Color成员,比较两个ColorPoint就是比较这两个成员的值。而不是跟上面那样,因为使用了继承,ColorPoint可以和Point进行比较。
     
    public class Point {
            private final int x;
            private final int y;
    
            public Point(int x, int y) {
                   this.x = x;
                   this.y = y;
           }
    
            @Override
            public boolean equals(Object o) {
                   if (!(o instanceof Point))
                          return false ;
                  Point p = (Point) o;
                   return p.x == x && p.y == y;
           }
    
            // See Item 9
            @Override
            public int hashCode() {
                   return 31 * x + y ;
           }
    }
    
    public enum Color {
            RED, ORANGE , YELLOW, GREEN, BLUE, INDIGO , VIOLET
    }
    
    public class ColorPoint {
            private final Point point ;
            private final Color color ;
    
            public ColorPoint(int x, int y, Color color) {
                   if (color == null)
                          throw new NullPointerException();
                   point = new Point(x, y);
                   this.color = color ;
           }
    
            /**
            * Returns the point -view of this color point.
            */
            public Point asPoint() {
                   return point ;
           }
    
            @Override
            public boolean equals(Object o) {
                   if (!(o instanceof ColorPoint))
                          return false ;
                  ColorPoint cp = (ColorPoint) o;
                   return cp.point .equals(point ) && cp.color.equals( color);
           }
    
            @Override
            public int hashCode() {
                   return point .hashCode() * 33 + color.hashCode();
           }
    }
    
    一致性
     
    “ 如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象(或者两个都)被修改了。换句话说,可变对象在不同的时候可以与不同的对象相等,而不可变对象则不会这样。”
     
    “无论类是否是不可变的,都不要使equals方法依赖于不可靠的资源。如果违反了这条禁令,要想满足一致性的要求就十分困难了。”比如,比较的时候,依赖解析到的IP地址:
     
    1. @Override
    2. public boolean equals(Object obj) {
    3. if (!(obj instanceof InetAddress)) {
    4. return false;
    5. }
    6. return Arrays.equals(this.ipaddress, ((InetAddress) obj).ipaddress);
    7. }
    在比较两个URL是否相等时,java.net.URL使用了这样的依据,两个URL对应的IP地址是否是相等的,如果相等,则它们两个是同一个URL。它这个实现,依赖了不可靠的资源,IP地址。IP地址,随着时间的推移,当时相等的两个URL,会不相等,因为,它们在比较的时候,还要解析IP地址。
     
    非空性
     
    是指比较的两个对象,不能为空。但是,在重写equals方法时,无需使用 if( o == null ) return false。因为,测试两个对象是否相等时,可以直接使用instanceof运算符:
    	* 
    @Override
    	*  public boolean equals(Object obj) {
    	*  if (!(obj instanceof InetAddress)) {
    	*  return false;
    	*  }
    	*  return Arrays.equals(this.ipaddress, ((InetAddress) obj).ipaddress);
    	*  }  
    instanceof运算符,包含了null的检测,也就是说,obj如果为空,那么 obj instanceof InetAddress是false的,这样就不会进行具体的比较,直接返回false.
     
    来自《effective java》中的诀窍,实现高质量的equals的诀窍:
     
    1.使用 == 操作符检查 “参数是否为这个对象的引用”。如果是,则返回true。这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做。使用==操作符,将当前独享的引用与参数进行比较。
     
     
    2.使用instanceof操作符检查”参数是否为正确的类型”。如果不是,则返回false。一般说来,所谓“正确的类型”是指equals方法所在的那个类。有些情况下,是指该类所实现的某个接口。
     
    3.把equals方法传入的参数转换成正确的类型。
     
    4.对于该类中的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回true;否则返回false。
     
    5.对于float,double类型的关键域,使用Float.compare,Double.compare来进行比较,而不使用==,这是因为存在Float.NaN,-0.0f以及类似的double常量。
     
    6.对于关键域的类型是非float,double类型的基本类型,使用==进行比较。
    --------------------------------------------------------------------------------------
    注意:
     
    1.覆盖equals时总要覆盖hashCode方法,这是为了让该类可以在使用了散列的集合中使用,比如Map.
     
    2.不要将equals声明中的Object对象替换为其它的类型。因为,我们是在覆盖java.lang.object的equals方法,而不是重载。覆盖表示,某个方法,在子类表现的行为是不同的;而重载,则是提供了两种行为,一个方法。
     
    3.不要企图让equals方法过于智能。如果只是简单地测试域中的值是否相等,则不难做到准守equals约定。如果想过度地去寻求各种等价关系,则很容易陷入麻烦之中。
     
  • 相关阅读:
    CentOS6.5下安装Redis2.8.6和phpredis2.2.4扩展
    Centos系统安装 phpredis 扩展
    在centos6.3用yum安装redis
    CentOS 安装图形化界面方法
    解决Eclipse中文乱码
    apache mod_alias模块功能介绍
    php 使用serialize() 和 unserialize() 让对象成超级变量
    eclipse导入php项目
    php多文件上传类(含示例)
    PHP多图片上传类推荐
  • 原文地址:https://www.cnblogs.com/ttylinux/p/4363964.html
Copyright © 2020-2023  润新知