一、如何重写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
如内容有更新,通过链接可以访问最新的内容。