说明:
本系列博客是本人在工作中遇到的一些问题的整理,其中有些资料来源网络博客,有些信息来自出版的书籍,掺杂一些个人的猜想及验证,总结,主要目的是方便知识的查看,并非纯原创。本系列博客会不断更新。原创不容易,支持原创。对于参考的一些其他博客,会尽量把博客地址列在博客的后面,以方便知识的查看。
本篇博客可以看做是《Effective Java中文版第2版》第三章(对于所有对象都通用的方法)第八条(覆盖equals时请遵守通用约定)的读书笔记,其中掺杂了一些个人的想法及验证。
equals方法定义在Object类中,以下是Object类中equals方法的定义:
public boolean equals(Object obj) {
return (this == obj);
}
Object类中的equals方法默认比较的是对象的内存地址,只有内存地址相同的的两个对象才被认为是相同的;
什么时候需要覆盖Object类中的equals方法?
当一个类具有自己特有的"逻辑相等"概念(不同于对象等同概念,即 ==),而且父类还没有覆盖equals方法以实现期望的行为,这时就需要覆盖Object类中的equals方法。
"逻辑相等"是指对象在逻辑上,或者说在业务范围内是相等的,而不是指它们是否指向同一个对象。举个例子,比如说业务上规定,学生不能同名,那么表示学生实体的两个对象,其name属性值是一样的话,就可以认为这两个对象是相同的;再比如,平面上的点,只要它们的横坐标和纵坐标分别相等,那么就可以认为这些点是相同的。
覆盖equals方法时必须遵守的通用约定:
1、自反性:对于任何非null的引用值x,x.equals(x)必须返回true;
2、对称性:对于任何非null的引用值x,y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true;
3、传递性:对于任何非null的引用值x,y,z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true;
4、一致性:对于任何非null的引用值x,y,只要equals的比较操作在对象中用到的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false;
5、对于任何非null的引用值x,x.equals(null)必须返回false。
一个类的实例会被频繁地传递给另一个类的实例,有许多类,包括所有的集合类,都依赖于传递给它们的对象是否遵守了equals约定。
我们来看下书中的一个例子:
public class CaseInsensitiveString {
private String s = null;
public CaseInsensitiveString(String s){
if (null == s){
throw new IllegalArgumentException();
}
this.s = s;
}
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString){
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
}
if (o instanceof String){
return s.equalsIgnoreCase((String)o);
}
return false;
}
}
这是一个大小写不敏感的String类,其中覆盖了父类的equals方法,对String类型的入参做了兼容,看下测试类:
public class CaseInsensitiveStringTest {
public static void main(String[] args) {
String s = "polish";
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
System.out.println("cis.equals(s):" + cis.equals(s));
System.out.println("s.equals(cis):" + s.equals(cis));
List<CaseInsensitiveString> list = new ArrayList<CaseInsensitiveString>();
list.add(cis);
System.out.println("list.contains(s):" + list.contains(s));
}
}
控制台输出:
cis.equals(s):true因为对String类型的入参做了兼容,所以返回true
s.equals(cis):false由于String类的equals方法并没有对CaseInsensitiveString类型做兼容,类型不匹配,因此返回false;我们看下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;
}
首先比较两个对象的引用是否相等,其次判断入参类型是否是String或String的子类,然后判断字符数是否相等,如果这些条件都相等,再依次比较字符序列中的每一个对应位置的字符是否相等,所有这些条件都满足才返回true,否则返回false,s.equals(cis)不满足anObject instanceof String,因此返回false;
list.contains(s):false,我们来看下List的实现类ArrayList中的contains方法实现:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
再看indexOf(Object o)方法:
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
来分析一下,indexOf(Object o)方法入参s不为null,执行else分支,循环,判断o.equals(elementData[i]),这里的对象o实际是String类型,elementData[i]是CaseInsensitiveString类型,由于String类的equals方法没有对CaseInsensitiveString做兼容,因此indexOf(Object o)方法最终返回-1,导致contains方法返回false,如果ArrayList中的indexOf方法中o.equals(elementData[i])反过来,elementData[i].equals(o),那么contains方法就会返回true。
CaseInsensitiveString的equals方法实现违反了对称性约束,list.contains(s)返回结果取决于具体的实现。
再看书中的另一个例子,父类与子类的equals问题:
/**
* Created with IntelliJ IDEA.
* User: yejunwu123@gmail.com
* Date: 2014-08-19 16:24
* Description:
*/
public class Point {
private int x;
private int y;
public Point(int x,int y){
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point)){
return false;
}
Point p = (Point)obj;
return this.x == p.x && this.y == p.y;
}
}
/**
* Created with IntelliJ IDEA.
* User: yejunwu123@gmail.com
* Date: 2014-08-19 16:28
* Description:
*/
public class ColorPoint extends Point {
private Color color;
public ColorPoint(int x,int y,Color color){
super(x,y);
this.color = color;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint)){
return false;
}
return super.equals(obj) && this.color == ((ColorPoint)obj).color;
}
}
/**
* Created with IntelliJ IDEA.
* User: yejunwu123@gmail.com
* Date: 2014-08-19 16:30
* Description:
*/
public enum Color {
RED,BLUE
}
看下测试类:
/**
* Created with IntelliJ IDEA.
* User: yejunwu123@gmail.com
* Date: 2014-08-19 16:36
* Description:
*/
public class PointTest {
public static void main(String[] args) {
Point point = new Point(1,2);
ColorPoint colorPoint = new ColorPoint(1,2,Color.RED);
System.out.println("point.equals(colorPoint):" + point.equals(colorPoint));
System.out.println("colorPoint.equals(point):" + colorPoint.equals(point));
}
}
控制台输出:
point.equals(colorPoint):true
colorPoint.equals(point):false
这个不难理解,ColorPoint 类的equals方法中判断入参类型obj instanceof ColorPoint返回false,因此colorPoint.equals(point)返回false。关于instanceof的一些说明可以参看博客http://ywu.iteye.com/blog/2105750
将ColorPoint 类的equals修改:
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Point)){
return false;
}
if (!(obj instanceof ColorPoint)){
//obj is a Point
return obj.equals(this);
}
//obj is a ColorPoint
return super.equals(obj) && this.color == ((ColorPoint)obj).color;
}
再运行下测试:
public class PointTest {
public static void main(String[] args) {
Point point = new Point(1,2);
ColorPoint colorPoint = new ColorPoint(1,2,Color.RED);
System.out.println("point.equals(colorPoint):" + point.equals(colorPoint));
System.out.println("colorPoint.equals(point):" + colorPoint.equals(point));
}
}
控制台输出:
point.equals(colorPoint):true
colorPoint.equals(point):true
这个结果不难分析出来
修改下测试类:
ColorPoint cp1 = new ColorPoint(1,2,Color.RED);
Point p2 = new Point(1,2);
ColorPoint cp3 = new ColorPoint(1,2,Color.BLUE);
System.out.println("cp1.equals(p2):" + cp1.equals(p2));
System.out.println("p2.equals(cp3):" + p2.equals(cp3));
System.out.println("cp1.equals(cp3):" + cp1.equals(cp3));
不难分析出,当两个类型都为ColorPoint 时,在判断this.color == ((ColorPoint)obj).color时为false,因此
cp1.equals(cp3)为false。
这种情形就违背了传递性,因此在子类继承父类,覆盖equals时需要注意。
《Effective Java中文版第2版》一书中说,我们无法在扩展可实例化类的同时,既增加值组件,又保留equals约定,除非愿意放弃面向对象抽象所带来的优势。对于这种情形,书中提出了复合优于继承的策略,有兴趣的可以看下书。
可以在一个抽象类的子类中添加值组件,而不会违反equals约定。
《Effective Java中文版第2版》中的建议:
1、使用==操作符检查 "参数是否为这个对象的引用"。这项操作不是必须的,因为在进行类型判断时null instanceof 任何类型将返回false,这种方式是作为一种性能优化方式,如果比较操作比较昂贵的话;
2、使用instanceof操作符检查 "参数是否为正确的类型",正确的类型一般是指equals方法所在的那个类;
3、把参数转换成正确的类型;
4、对于类中的每个"关键域",检查参数中的域是否与该对象中对应的域相匹配,对于float与double以外的基本类型,可以使用==比较,引用类型可以递归的调用equals方法,float类型可以调用Float.compare方法,double类型可以调用Double.compare方法比较,对于数组类型,需要把以上规则应用到每个元素,如果数组中的每个元素都很重要,可以使用Arrays.equals方法。
如果这些测试都成功,则返回true,否则返回false。
域的比较顺序可能会影响到equals方法的性能,应该比较最有可能不一致的域,或开销最低的域。
覆盖equals方法时总要覆盖hashCode方法,关于hashCode,参看http://ywu.iteye.com/blog/2106466。
相关推荐
本文还介绍了定义对象的相等性、实施equals()和hashCode()的需求、编写自己的equals()和hashCode()方法。通过统一定义equals()和hashCode(),可以提升类作为基于散列的集合中的关键字的使用性。
equals的源代码: public boolean equals(Object obj){ return (this==obj); } 那么s1.equals(s2)的返回值不就是 (s1==s2) 吗? 但是s1.equals(s2);结果为true s1==s2; 结果为false 问题三: class Student{ ...
equals()和hashcode()这两个方法都是从object类中继承过来的。当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法.
能够加强对java中equals与==区别的理解。
equals():反映的是对象或变量具体的值,即两个对象里面包含的值--可能是对象的引用,也可能是值类型的值。 hashCode():计算出对象实例的哈希码,并返回哈希码,又称为散列函数。根类Object的hashCode()方法的...
java_equals用法,用来熟悉重写equals方法的
==和equals方法究竟有什么区别? == 操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符。 ...
equals方法重写作业,Students类,有三个属性id ,name ,grade。还有一个测试类用于测试创建了三个对象前两各对象的数据完全一样,第三个对象的数据不同。使用equals方法比较。并输出结果。
Java中equals方法隐藏的陷阱
重载equals方法示例重载equals方法示例重载equals方法示例重载equals方法示例重载equals方法示例
值类型是存储在内存中的堆栈(以后简称栈),而引用类型的变量在栈中仅仅是存储引用类型变量的地址,而其本身则存储在堆中。...equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同。
java 资料 equals 与== 的区别
知识点 比较运算符==和equals方法的比较 知识点 比较运算符==和equals方法的比较
java中equals和==的区别.doc java中equals和==的区别.doc
java中比较值大小,==和equals的区别,基本数据类型和引用数据类型比较值方法
String中==与equals区别验证
HashCode相同equals不同的2位字符集合算法 另附ASCII码表
Java重写equals同时需要重写hashCode的代码说明,以及如何重写hashCode方法,此代码演示按照effective java书籍说明的重写思路。代码中演示了使用集合存储对象,并且对象作为key,需重写equals和hashCode.
equals与==之间的区别,记事本详解
详细介绍和讲解Java中的==和equals区别