• 重载equals方法时要遵守的通用约定--自反性,对称性,传递性,一致性,非空性


    本文涉及到的概念
    1.为什么重载equals方法时,要遵守通用约定
    2.重载equals方法时,要遵守哪些通用约定
     
    为什么重载equals方法时,要遵守通用约定
    Object类的非final方法都有明确的通用约定,这些方法是被设计成被重载的。重载时,如果不遵守通用约定,那么,其它依赖于这些通用约定的类(例如HashMap和HashSet)就无法结合该类一起正常工作----<<effective java>>
     
     
    quals方法实现了等价关系,重载时要遵守的通用约定:
    a.自反性(reflexive)  对于任何非null的引用值x, x.equals(x)必须返回true。
    b.对称性(symmetric)  对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
    c.传递性(transitive)   对于任何非null的引用值x,y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)返回true
    d.一致性      对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回
    true,或者一致地返回false
    e.对于任何非null的引用值x,x.equals(null)必须返回false
     
    a.自反性
    基本上不会违背这一条规定。如果违背了的话,将一个引用添加到一个集合中,然后,调用集合的contains(x)方法,它会返回false。x.equals(x)不等于true,导致contains(x)方法返回false。
     
    b.对称性
    对于任何非null的引用值x和y, x.equals(y)返回true, y.equals(x)也要返回true
    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));
    }
    }
    可以把上述的例子代码,代入对称性公式,CaseInsensitivesString为x, String为y, CaseInsensitivesString为y.
    x.equals(y),y.equals(x)都为true,当y是CaseInsensitivesString类型时;当y为String类型时,y.equals(x),就为false。
     
    String类的equals方法的实现: stringInstance.equals(CanseInsensitivesStringIns),它会返回false,因为x不是String类型
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = count;
            if (n == anotherString.count) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = offset;
            int j = anotherString.offset;
            while (n-- != 0) {
                if (v1[i++] != v2[j++])
                return false;
            }
            return true;
            }
        }
        return false;
        }
    c.传递性
    equals预定的第三个要求是,如果一个对象等于第二个对象,并且第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。
    ----<<effective java>>
    重写类的equals方法后,使用x.equals(y), y.equals(z),x.equals(z),去检验。如果发现不符合,则违反了该通用约定,那么,就要重新实现。
     
    下面的例子,违反了传递性:
    父类Point,Point类之间的比较,重写equals方法,比较内容是否相等,不是比较引用地址是否相等。
    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;
    }
    
    // Broken - violates Liskov substitution principle - Pages 39-40
    // @Override public boolean equals(Object o) {
    // if (o == null || o.getClass() != getClass())
    // return false;
    // Point p = (Point) o;
    // return p.x == x && p.y == y;
    // }
    
    // See Item 9
    @Override
    public int hashCode() {
    return 31 * x + y;
    }
    }

    创建一个子类,继承Point类

    import java.awt.Color;
    
    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!
    //x为Point,y为ColorPoint
    // x.equals(y)为true,但是y.equals(x)为false
    @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;
    // }
    //p1.euqlas(p2),p2.equals(p3),p1.equals(p3)
    //输出结果:输出结果 true, true ,false
    
    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));
    }
    }
    p1和p2的比较,不考虑颜色;p2和p3的比较,也不考虑颜色;p1和p3的比较考虑颜色,于是p1和p3不相等。违反了传递性原则,x.equals(y),y.equals(z).x.equals(z)
     
    如何解决这个问题?
    事实上,这是面向对象语言中关于等价关系的一个基本问题。我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。
    也就是,我们无法在扩展父类,然后,同时在子类中保留父类和子类的equals约定。
     
    解决办法:
    a.复合优先于继承
    b.父类是抽象类(没有任何值组件),子类添加值域
     
    使用复合的方式来解决
     
    // Simple immutable two-dimensional integer point class - Page 37
    
    import java.util.*;
    
    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 }
    
    // Adds a value component without violating the equals contract - Page 40
    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();
      }
    }
    
    /////////////////////////////////////////////////////////////////////////
    public class Test {
    
    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));
    }
    }
    输出结果:
    false false
    false false false
     
  • 相关阅读:
    CentOS 7 最小化安装建议安装包
    何为“精通Java”
    初识设计模式、软件设计的六大原则
    Git——常用场景解析
    元素水平或垂直居中问题
    书写静态页面的那些事儿。。。
    Position定位相关知识了解
    溢出处理、盒子模型、背景图片、float(浮动)
    CSS颜色、单位、文本样式
    CSS样式表及选择器相关内容(二)-伪类与伪元素选择器
  • 原文地址:https://www.cnblogs.com/ttylinux/p/6539436.html
Copyright © 2020-2023  润新知