zoukankan      html  css  js  c++  java
  • 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约定。如果想过度地去寻求各种等价关系,则很容易陷入麻烦之中。
     
  • 相关阅读:
    第4月第1天 makefile automake
    第3月30天 UIImage imageWithContentsOfFile卡顿 Can't add self as subview MPMoviePlayerControlle rcrash
    第3月第27天 uitableviewcell复用
    learning uboot fstype command
    learning uboot part command
    linux command dialog
    linux command curl and sha256sum implement download verification package
    learning shell script prompt to run with superuser privileges (4)
    learning shell get script absolute path (3)
    learning shell args handing key=value example (2)
  • 原文地址:https://www.cnblogs.com/ttylinux/p/4363964.html
Copyright © 2011-2022 走看看