• 重写equals时,遵守的规定


     

    0 正确的equals方法

    [java] view plain
    1. public class MyClass {  
    2.     // 主要属性1  
    3.     private int primaryAttr1;  
    4.     // 主要属性2  
    5.     private int primaryAttr2;  
    6.     // 可选属性  
    7.     private int optionalAttr;  
    8.       
    9.     // 延迟加载,缓存散列码  
    10.     private volatile int hashCode = 0;  
    11.       
    12.     @Override  
    13.     public int hashCode() {  
    14.         if(hashCode == 0) {  
    15.             int result = 17;  
    16.             result = 37*result + primaryAttr1;  
    17.             result = 37*result + primaryAttr2;  
    18.             hashCode = result;  
    19.         }  
    20.         return hashCode;  
    21.     }  
    22.     @Override  
    23.     public boolean equals(Object obj) {  
    24.         if(obj == this) {  
    25.             return true;  
    26.         }  
    27.         if(!(obj instanceof MyClass)) {  
    28.             return false;  
    29.         }  
    30.         MyClass myClass = (MyClass) obj;  
    31.         return myClass.primaryAttr1 == primaryAttr1 &&  
    32.                myClass.primaryAttr2 == primaryAttr2;  
    33.     }  
    34.       
    35. }  

    1 在改写equals时要遵守通用约定

    1.1 不必改写equals的情况:

    1)一个类的每个实例本质上都是惟一的。
    2)不关心一个类是否提供了“逻辑相等”的测试功能。
    3)超类已经改写的equals,从超类继承的行为对于子类也是合适的。
    4)一个类是私有的,或者是包级私有的,并且可以确定它的equals方法永远也不会被调用。

    1.2 需要改写Object.equals的情况:

    当一个类有自己特有的“逻辑相等”概念,而且父类也没有改写equals以实现期望的行为。通常适合于value class的情形。
    改写equals也使得这个类的实例可以被用作map的key或者set的元素,并使map和set表现出预期的行为。

    1.3 改写equals要遵守的通用约定(equals方法实现了等价关系):

    1)自反性:x.equals(x)一定返回true
    2)对称性:x.equals(y)返回true当且仅当y.equals(x)
    3)传递性:x.equals(y)且y.equals(z),则x.equals(z)为true
    4)一致性:若x.equals(y)返回true,则不改变x,y时多次调用x.equals(y)都返回true
    5)对于任意的非空引用值x,x.equals(null)一定返回false。

    当重写完equals方法后,应该检查是否满足对称性、传递性、一致性。(自反性、null通常会自行满足)

    下面是一个示例:

    [java] view plain
    1. public class Point {  
    2.     private final int x;  
    3.     private final int y;  
    4.     public Point(int x, int y) {  
    5.         this.x = x;  
    6.         this.y = y;  
    7.     }  
    8.     public boolean equals(Object o) {  
    9.         if(!(o instanceof Point))  
    10.             return false;  
    11.         Point p = (Point)o;  
    12.         return p.x == x && p.y == y;  
    13.     }  
    14.     //...  
    15. }  

    [java] view plain
    1. import java.awt.Color;  
    2.   
    3. public class ColorPoint extends Point {  
    4.     private final Color color;  
    5.     public ColorPoint(int x, int y, Color color) {  
    6.         super(x, y);  
    7.         this.color = color;  
    8.     }  
    9.       
    10.     /* 不满足对称性 */  
    11.     public boolean equals(Object o) {  
    12.         if(!(o instanceof ColorPoint))  
    13.             return false;  
    14.         ColorPoint cp = (ColorPoint)o;  
    15.         return super.equals(o) && cp.color == color;  
    16.     }  
    17.       
    18.     //...  
    19. }  

    [java] view plain
    1. import java.awt.Color;  
    2.   
    3. public class Test {  
    4.     public static void main(String arg[])  
    5.     {  
    6.         System.out.println("检验对称性:");  
    7.         Point p = new Point(1, 2);  
    8.         ColorPoint cp = new ColorPoint(1, 2, Color.RED);  
    9.         System.out.println(p.equals(cp));  
    10.         System.out.println(cp.equals(p));  
    11.           
    12.         System.out.println("检验传递性:");  
    13.         ColorPoint p1 = new ColorPoint(1, 2, Color.RED);  
    14.         Point p2 = new Point(1, 2);  
    15.         ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);  
    16.         System.out.println(p1.equals(p2));  
    17.         System.out.println(p2.equals(p3));  
    18.         System.out.println(p1.equals(p3));  
    19.           
    20.     }  
    21. }  
    此时的输出为:
    [plain] view plain
    1. 检验对称性:  
    2. true  
    3. false  
    4. 检验传递性:  
    5. false  
    6. true  
    7. false  
    这表示上述equals方法的写法违背了对称性,但没有违背传递性。之所以不满足对称性,是由于Point的equals方法在接收ColorPoint类型的参数时,会将其当做Point(忽略color属性)进行比较,而ColorPoint的equals方法接收Point类型参数时,判断其不是ColorPoint类型就直接返回false。因此,可以对equals方法作如下修改:
    [java] view plain
    1. /* 满足对称性,但牺牲了传递性  */  
    2. public boolean equals(Object o) {  
    3.     if(!(o instanceof Point))// 不是Point类型  
    4.         return false;  
    5.     if(!(o instanceof ColorPoint))// 是Point类型,但不是ColorPoint类型  
    6.         return o.equals(this);  
    7.                // 是ColorPoint类型  
    8.     ColorPoint cp = (ColorPoint)o;  
    9.     return super.equals(o) && cp.color == color;  
    10. }  

    此时的执行输出为:

    [plain] view plain
    1. 检验对称性:  
    2. true  
    3. true  
    4. 检验传递性:  
    5. true  
    6. true  
    7. false  
    这表示满足对称性,但牺牲了传递性。由于p1与p2,p2与p3的比较都没有考虑color属性,但是p1与p3比较则考虑了color属性。要在扩展(即继承)一个可实例化的类的同时,既要增加新的属性,同时还要保留equals约定,没有一个简单的方法可以做到。

    复合优先于继承。遵照这条原则,这个问题可以有很好的解决方案,不再让ColorPoint继承Point,而是在ColorPoint类中加入一个私有的Point域,以及一个公有的view方法,此方法返回对应的Point对象。

    [java] view plain
    1. import java.awt.Color;  
    2.   
    3. public class ColorPoint {  
    4.     private Point point;  
    5.     private final Color color;  
    6.     public ColorPoint(int x, int y, Color color) {  
    7.         point = new Point(x, y);  
    8.         this.color = color;  
    9.     }  
    10.       
    11.     // 返回ColorPoint的Point视图(view)  
    12.     public Point asPoint() {  
    13.         return point;  
    14.     }  
    15.       
    16.     public boolean equals(Object o) {  
    17.         if(!(o instanceof ColorPoint))  
    18.             return false;  
    19.         ColorPoint cp = (ColorPoint)o;  
    20.         return cp.point.equals(point) && cp.color.equals(color);  
    21.     }  
    22.       
    23.     // ...  
    24.       
    25. }  

    Java平台中有一些类是可实例化类(非抽象类)的子类,且加入了新属性。例如,java.sql.Timestamp继承自java.util.Date,并增加了nanoseconds域,Timestamp的equals违反了对称性,当Timestamp和Date用于集合中时可能出现问题。

    注意,可以在一个抽象类的子类中增加新的属性,而不会违反equals约定。因为不可能创建父类的实例,也就不会出现上述问题。

    2 不要将equals声明中的Object对象替换为其他类型

    [java] view plain
    1. public boolean equals(MyClass obj) {  
    2.     ...  
    3. }  
    上述代码,使用了具体的类MyClass作为参数,这会导致错误。原因在于,这个方法并没有重写(override)Object.equals方法,而是重载(overload)了它。某些情况下,这个具体化的equals方法会提高一些性能,但这样极有可能造成不易察觉的错误。

    3 改写equals时同时改写hashCode

    3.1 java.lang.Object的规范中,hashCode约定:

    1)在一个应用程序执行期间,如果一个对象的equals方法
    2)相等对象(equals返回true)必须具有相等的散列码
    3)不相等对象(equals返回false)调用hashCode方法,不要求必须产生不同的值;但是产生不同的值有可能提高散列表的性能。

    若改写equals时,未同时改写hashCode方法,则可能导致不满足第二条。

    3.2 改写hashCode的常用方法:

    1)把某个非零常数值,如17,保存在一个叫result的int类型变量中;
    2)对与对象中每个关键域f(equals方法中考虑的每一个域),完成以下步骤:
    2.1)为该域计算int类型的散列码c:
    2.1.1)若该域为boolean类型,计算(f?0:1);
    2.1.2)若该域为byte、char、short、int类型,计算(int)f;
    2.1.3)若该域为long,计算(int)(f ^ (f>>>32));
    2.1.4)若该域为float,计算Float.floatToIntBits(f);
    2.1.5)若该域为double,先计算Double.doubleToLongBits(f)得到一个long值,再按2.1.3对long值计算散列值;
    2.1.6)若该域为一个对象引用,且该类的equals方法通过递归调用equals的方式比较这个域,则同样对这个域递归调用hashCode方法。
    2.1.7)若该域为一个数组,则把每一个元素当做单独的域来处理。即对每个元素递归地应用上面6条,然后根据2.2的做法把这些散列值组合起来。
    2.2)按照下面的公式,把步骤2.1中得到的散列码c组合到result中:
    result = 37*result + c;
    3)返回result。
    4)考查重写后的hashCode方法是否满足“相等的实例具有相等的散列码”

    参考资料:

    《Effective Java》(2nd Edition) 第7条、第8条、第14条、第20条、第37条等

    该书上貌似目前就推荐使用复合而不是继承,所以最终解决重写equals的传递性的方案是复合。如果各路大神有好的方式麻烦告诉我,在此谢谢了

  • 相关阅读:
    vue:自定义指令
    vue 声明周期
    组件之间的通信
    vue动态组件keepalive
    vuecli的使用
    数据结构线性表(C#) 天高地厚
    HOOK钩子函数 天高地厚
    OSI模型 天高地厚
    HTTP 天高地厚
    说说JSONP 天高地厚
  • 原文地址:https://www.cnblogs.com/cunkouzh/p/5364910.html
Copyright © 2020-2023  润新知