您的位置:首页 > 编程语言 > Java开发

如何重写equals和hashCode方法

2014-11-23 00:00 211 查看
摘要: equals方法和hashCode的重写规则介绍。

一、如何重写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

如内容有更新,通过链接可以访问最新的内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java equals hashCode