java中equals使用总结-mile米乐体育
前段时间一直在工作中使用java,由于有一些c 功底,于是简单看了一下java相关的语法便开始编写代码,结果在创建一个自定义类,并将自定义类放入arraylist中,之后查找arraylist是否有此元素的时候,发现怎么也查询不到对应的元素。在网上搜了一下资料,发现原因是没有重写对象的equals()方法,导致无法查找到对应的对象。之后由查了与之联系的相关资料,便有了以下的总结。
这篇总结的形式是提出个问题,然后给出问题的答案。这是目前学习知识的一种尝试,可以让学习更有目的。
q1.什么时候应当重写对象的equals方法?
答:一般在我们需要进行值比较的时候,是需要重写对象的equals方法的。而例外情况在《effective java》的第7条“在改写equals的时候请遵守通用约定”中清楚描述了。
我们知道,在java中,每个对象都继承于object.如果不重写,则默认的equals代码如下所示:
public boolean euqals(object obj){ return this == obj; }
由上面的代码可以看出,equal默认是使用“==”来判断两个对象是否相等。两个对象使用“==”比较的是对象的地址,只有两个引用指向的对象相同的时候,“==”才返回true。所以,在开头的例子中,就需要重写equals方法,让两个对象有equals的时候。
q2.如何重写equals?
答:首先,当改写equals方法时,需要保证满足它的通用约定。这些约定如下所示:
- 自反性,对于任意的引用值x,x.equals(x)一定为true。
- 对称性,对于任意的引用值x和y,当且仅当y.equals(x)时,x.equals(y)也一定返回true.
- 传递性,对于任意的引用值x,y,z。如果x.equals(y)返回true,y.euqals(z)返回true,则x.equals(z)也返回true。
- 一致性,对于任意的引用值x和y,如果用于equals比较的对象信息没有修改,那么,多次调用x.equals(y)要么一致返回true,要么一致返回false.
- 非空性,所有的对象都必须不等于null。
其实我觉的一个简单的方法是参照string的equals方法即可,官方出版,满足各种要求。其代码如下所示
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; }
函数的解释如下所示:
- 使用==检查“实参是否是指向对象的一个引用”。
- 使用instanceof检查实参是否和本对象同类,如果不同类,就不相等。
- 将实参转换为正确的类型。
- 根据类的定义,检查实现此对象值相等的各个条件。
更详细的信息,还是请看《effective java》的第7条“在改写equals的时候请遵守通用约定”。
q3.修改equals时需要注意什么?
答:大致需要注意以下几点:
若修改equals方法,也请修改hashcode方法
首先这个是语言的一个约定,这么做的一个原因是当此对象作为哈希容器的元素时,需要依赖hashcode,对象默认的hashcode是返回一个此对象特有的hashcode,不同的对象的hashcode返回值是不一样的,而哈希容器处理元素时,是按照对象的哈希值将对象分配到不同的桶中,若我们不重写对象的hashcode,那么值相等的对象产生的哈希值也会不同,这样当在哈希容器中查找时,会找不到对应的元素。
更详细的信息请看《effective java》的第8条“改写equals时总是要改写hashcode”。
重写时保证函数声明的正确
请注意equals的声明是
public boolean equals(object obj)
参数类型是object,如果参数类型是此对象类型的话,如下:
class point{ final int x; final int y; public void point(int x, int y) this.x = x; this.y = y; } public boolean euqals(point obj){ return (this.x == obj.x && this.y == obj.y); } }
下面代码执行是按照我们的预期执行的。
point a(1, 2); poinr b(1, 2); system.out.println(a.equals(b));// 输出true
但是如果将类a放入容器中,则会出问题
import java.util.hashset; hashsetcoll = new hashset (); coll.add(a); system.out.println(coll.contains(b));// 输出false
这是由于hashset中的contains方法中调用的是equals(object obj),而point中的equals(object obj)仍是object的equals,这个方法在前面已经说过了,比较的是对象的地址,所以在coll中调用contains(b)时,当然得不到true。
当有继承关系时注意equals的正确
当一个类重写equals方法后,另一个类继承此类,此时,可能会违反前面说到的对称性,代码如下所示:
public class coloredpoint extends point { private final color color; public coloredpoint(int x, int y, color color) { super(x, y); this.color = color; } @override public boolean equals(object other) { boolean result = false; if (other instanceof coloredpoint) { coloredpoint that = (coloredpoint) other; result = (this.color.equals(that.color) && super.equals(that)); } return result; } }
当我们作比较时
point p = new point(1, 2); coloredpoint cp = new coloredpoint(1, 2, color.red); system.out.println(p.equals(cp)); //输出ture system.out.println(cp.equals(p)); //输出false
原因是当调用point.equals的时候,只比较了point的x和y坐标,同时coloredpoint也是point类型,所以上面第三行代码相等,而调用coloredpoint的时候,point不是coloredpoint类型,这样就导致第四行代码输出false。
若我们忽略color的信息来比较呢,例如将coloredpoint的equals方法改为:
@overwrite public boolean equals(object obj){ if((obj instanceof point)){ return false; } if(!(obj instanceof coloredpoint)){ return obj.equals(this); } return super.equals(obj) && ((coloredpoint)obj).color == color; }
这样就保证了对称性,但是却违反了传递性,即下面的情况:
coloredpoint cp1 = new coloredpoint(1, 2, color.red); point p = new point(1, 2); coloredpoint cp2 = new coloredpoint(1, 2, color.blue); system.out.println(cp1.equals(p)); //true system.out.println(p.equals(cp2)); //true system.out.println(cp1.equals(cp2)); //false
面对这种情况,大致有两种mile米乐体育的解决方案,一种酷壳的文章–如何在java中避免equals方法的隐藏陷阱的最后一条,断绝了point和coloredpoint相等的可能,这是一种处理方法,认为point和coloredpoint是不同的。另一种方法是effective java上提出的,使用聚合而不是继承,将point作为coloredpoint的一个成员变量。目前我倾向于这种方法,因为聚合比继承更灵活,耦合更低。这种方法的代码如下所示:
class coloredpoint{ private final point point; private final color color; public point aspoint(){ return point; } public boolean equals(object obj){ boolean ret = false; if(obj instanceof coloredpoint){ coloredpoint that = (coloredpoint)obj; ret = that.point.equals(point) && color.equals(that.color); } return ret; } }
当coloredpoint需要比较坐标时,可以调用aspoint方法来转化为坐标进行比较。其他情况比较坐标和颜色,这样就可以解决上面关于对称性和传递性的问题了。
以上就是全文的内容,由于水平有限,文章中难免会有错误,希望大家指正。谢谢