如何重写equals和hashCode方法

2014/11/23 21:27
阅读数 220

一、如何重写equals方法

1、为什么要重写equals方法?

    判断两个对象在逻辑上是否相等,如根据类的成员变量来判断两个类的实例是否相等,而继承Object中的equals方法只能判断两个引用变量是否是同一个对象。这样我们往往需要重写equals()方法。

    我们向一个没有重复对象的集合中添加元素时,集合中存放的往往是对象,我们需要先判断集合中是否存在已知对象,这样就必须重写equals方法。

2、重写equals方法的要求?

    1)自反性:对于任何非空引用x,x.equals(x)应该返回true。

    2对称性:对于任何引用x和y,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。

    3传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。

    4一致性:如果x和y引用的对象没有发生变化,那么反复调用x.equals(y)应该返回同样的结果。

    5非空性:对于任意非空引用x,x.equals(null)应该返回false。

3、怎样重写equals方法?

    1使用==操作符检查“实参是否为指向对象的一个引用”。

    2判断实参是否为null

    3使用instanceof操作符检查“实参是否为正确的类型”。

    4把实参转换到正确的类型。

    5对于该类中每一个“关键”域,检查实参中的域与当前对象中对应的域值是否匹配。

        对于既不是float也不是double类型的基本类型的域,可以使用==操作符进行比较;

        对于对象引用类型的域,可以递归地调用所引用的对象的equals方法;

        对于float类型的域,先使用Float.floatToIntBits转换成int类型的值,然后使用==操作符比较int类型的值;

        对于double类型的域,先使用Double.doubleToLongBits转换成long类型的值,然后使用==操作符比较long类型的值。

    6当你编写完成了equals方法之后,应该问自己三个问题:它是否是对称的、传递的、一致的?(其他两个特性通常会自行满足)如果答案是否定的,那么请找到这些特性未能满足的原因,再修改equals方法的代码。

4、重写equals的案例研究

 /**

     * 二维点类

     */

    public class Point {

         private final int x;

         private final int y;

         public Point(int x,int y) {

              this.x = x;

              this.y = y;

         }

    }

    重写equals

    public boolean equals(Object obj) {

          if(! (obj instanceof Point)) {

               return false;

          }

          Point p = (Point) obj;

          return p.x == x && p.y == y;

     }

    验证:

    public static void main(String[] args) {

          Point p1 = new Point(1,2);

          Point p2 = new Point(1,2);

          Point p3 = new Point(1,2);

          System.out.println("(1) p1.equals(p1):" + p1.equals(p1));

          System.out.println("(2) p1.equals(p2):" + p1.equals(p2));

          System.out.println("(3) p2.equals(p1):" + p2.equals(p1));

          System.out.println("(4) p2.equals(p3):" + p2.equals(p3));

          System.out.println("(5) p1.equals(p3):" + p1.equals(p3));

          System.out.println("(6) p1.equals(null):" + p1.equals(null));

     }

    打印结果:

    (1) p1.equals(p1):true

    (2) p1.equals(p2):true

    (3) p2.equals(p1):true

    (4) p2.equals(p3):true

    (5) p1.equals(p3):true

    (6) p1.equals(null):false

    分析:

    由(1)可以验证自反性

    由(2)、(3)可以验证对称性

    由(1)、(4)、(5)可以验证传递性

    由(6)可以验证非空性


    此时,我们想再扩展这个类,给它增加颜色信息:

    public class ColorPoint extends Point {

            private Color color;

            public ColorPoint(int x,int y,Color color) {

                    super(x,y);

                    this.color = color;

            }

    }

    重写equals:

    public boolean equals(Object obj) {

          if(!(obj instanceof ColorPoint)) {

               return false;

          }

          ColorPoint cp = (ColorPoint)obj;

          return super.equals(obj) && cp.color == color;

     }

    验证:

    public static void main(String[] args) {

          Point p = new Point(1, 2);

          ColorPoint cp = new ColorPoint(1, 2, Color.RED);

          System.out.println(p.equals(cp));//true

          System.out.println(cp.equals(p));//false

     }

    这里验证的是,一个无色点和有色点的对比,两种情形结果是不一样的,很明显这种方式的equals方法违反了对称性。

    假如让ColorPoint.equals在进行“混合比较”的时候忽略颜色信息呢?

     public boolean equals(Object obj) {

          if(!(obj instanceof Point)) {

           return false;

      }

      //如果obj是一个无色点,就忽略颜色信息

      if(!(obj instanceof ColorPoint)) {

           return obj.equals(this);

      } else {

      //如果obj是一个有色点,就做完整的比较

       ColorPoint cp = (ColorPoint) obj;

           return super.equals(obj) && cp.color == color;

       }

    }

  验证:

    public static void main(String[] args) {

          ColorPoint p1 = new ColorPoint(1, 2, Color.RED);

          Point p2 = new Point(1, 2);

          ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);

          System.out.println(p1.equals(p2)); //true

          System.out.println(p2.equals(p1)); //true

          System.out.println(p2.equals(p3)); //true

          System.out.println(p1.equals(p3)); //false

     }

 这种重写方式虽然满足了对称性,却牺牲了传递性,那么该怎么解决呢?


     事实上,这是面向对象语言中关于等价关系的一个基本问题。要想在扩展一个可实例化的类的同时,既要增加新的特征,同时还要保留equals约定,没有一个简单的办法可以做到这一点。新的解决办法就是不再让ColorPoint扩展Point,而是在ColorPoint中加入一个私有的Point域,以及一个公有的视图(view)方法。

    public class NewColorPoint {

         private Point p;

         private Color color;

         public NewColorPoint(Point p, Color color) {

              this.setP(p);

              this.setColor(color);

         }

 

         public Point getP() {

              return p;

         }

    }

    重写equals:

    public boolean equals(Object obj) {

          if(obj == this) {

               return true;

          }

          if(!(obj instanceof NewColorPoint)) {

               return false;

          }

          NewColorPoint ncp = (NewColorPoint) obj;

          return ncp.p.equals(p) && ncp.color.equals(color);

     }

     验证:

    public static void main(String[] args) {

          Point p = new Point(1,2);

          NewColorPoint p1 = new NewColorPoint(p, Color.BLUE);

          NewColorPoint p2 = new NewColorPoint(p, Color.BLUE);

          NewColorPoint p3 = new NewColorPoint(p, Color.BLUE);

          System.out.println(p1.equals(p2)); //true

          System.out.println(p2.equals(p1)); //true

          System.out.println(p1.equals(p3)); //true

     }

    还有另外一个解决的办法就是把Point设计成一个抽象的类(abstract class),这样你就可以在该抽象类的子类中增加新的特征,而不会违反equals约定。因为抽象类无法创建类的实例,那么前面所述的种种问题都不会发生。


二、如何重写hashcode方法

1、为什么要重写hashCode方法?

    在用equals方法判断两个对象是否相等时,需要向下转型,效率很低,先通过比较hashCode的方式能提升效率。因为在判断两个对象是否相等的规则中:

    首先,判断两个对象的hashCode是否相等,如果不相等,则认为两个对象也不相等;如果相等,则继续判断两个对象的equals运算结果返回true,如果返回值是true,则认为两个对象相等;如果返回值是false,则认为两个对象不相等。

    由此,可知,优先比较hashCode是一种不错的策略。

2、如何重写hashCode方法?

    通常的做法是返回一个result,生成规则是:

    如果字段是boolean,则计算为(f?1:0);

    如果字段是byte,char,short,int,则计算为(int)f;

    如果字段是long,则计算为(int)(f^>>32);

    如果字段是float,则计算为Float.floatToLongBits(f);

    如果字段是一个引用对象,那么直接调用对象的hashCode方法,如果需要判空,可以加上如果为空,就返回0;

    如果字段是一个数组则需要遍历所有元素,按上面几种方法计算;

    当写完hashCode方法后,需要验证两个问题

    1)是否两个equal的实例,拥有相同的jhashCode ?

    2)两个不同的实例,是否拥有相同的hashCode ?

3、Eclipse自动生成的hashCode案例

   新建一个类如下:

    public class DemoHashCode {

         private byte bt;

         private short st;

         private int i;

         private long lg;

         private float ft;

         private double db;

         private boolean bool;

         private String string;

         private Point point;

         private int[] intArray;

    }

    通过eclipse自动生成的hashCode()如下:

    public int hashCode() {

          final int prime = 31;

          int result = 1;

          result = prime * result + (bool ? 1231 : 1237);

          result = prime * result + bt;

          long temp;

          temp = Double.doubleToLongBits(db);

          result = prime * result + (int) (temp ^ (temp >>> 32));

          result = prime * result + Float.floatToIntBits(ft);

          result = prime * result + i;

          result = prime * result + Arrays.hashCode(intArray);

          result = prime * result + (int) (lg ^ (lg >>> 32));

          result = prime * result + ((point == null) ? 0 : point.hashCode());

          result = prime * result + st;

          result = prime * result + ((string == null) ? 0 : string.hashCode());

          return result;

 }

参考资料

1、equals()方法的重写 :http://www.iteye.com/topic/269601

2、关于如何重写hashCode方法:http://allenwei.iteye.com/blog/228867

说明

    本篇博客还通过有道云笔记进行分享,分享链接为:http://note.youdao.com/share/?id=d8579fca450ab4f51e10267bd8f33f18&type=note

    如内容有更新,通过链接可以访问最新的内容。

展开阅读全文
加载中

作者的其它热门文章

打赏
0
1 收藏
分享
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部