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

java重写equals方法需要注意的几点

2017-04-04 08:29 309 查看
我们在写一个JavaBean时,经常会覆写equals方法,其目的是根据业务规则判断两个对象是否相等,这在DAO(Data
Access Object)层是经常用到的,具体操作时先从数据库中获得两个DTO(Data Transfer Object,数据传输对象),然后判断它们是否是相等的。

尽管object是一个具体类,涉及它就是为了扩展它,它所有的非final方法(equals,hashCode,toString,clone和finalize)都有一些通用的规定,因为它们被设计就是用来覆盖(override)的。任何一个类,它在覆盖这些方法的时候,都有责任遵守这些约定。
本篇文章主要讲解覆盖equals方法需要遵守的规定
覆盖equals方法看起来很简单,但是有许多覆盖方法会导致错误,并且后果很严重,最容易避免这类问题的方法就是不覆盖equals方法

那么什么时候应该覆盖Object.equals呢?
如果类具有自己特有的“逻辑相等"概念(不等同对象等同概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。
为什么equals()方法要重写?

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

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

怎样重写equals()方法?

重写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。

1、自反性原则

 在JavaBean中,经常会覆写equals方法,从而根据实际业务情况来判断两个对象是否相等,比如我们写一个person类,根据姓名来判断两个person类实例对象是否相等。代码如下:

public class Person {
private String name;

public Person(String name){
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person person= (Person) obj;
return name.equalsIgnoreCase(person.getName().trim());
}
return false;
}
public static void main(String[] args){
Person p1=new Person("张三");
Person p2=new Person("张三    ");
List<person> list = new ArrayList<person>();
list.add(p1);
list.add(p2);
System.out.println("是否包含张三:"+list.contains(p1));
System.out.println("是否包含张三:"+list.contains(p2));
}
}


 list中含有这个生成的person对象,结果应该为true,但是实际结果:    这里考虑了字符串空格的问题,去除前后的空格。

    是否包含张三:true

    是否包含张三:false

    第二个为什么会是false呢?原因在于list中检查是否含有元素时是通过调用对象的equals方法来判断的,也就是说 contains(p2)传递进去会依次执行p2.equals(p1)、p2.equals(p2),只要一个返回true,结果就是true。但是这里p2.equals(p2)返回的是false?由于我们对字符前后进行了空格的切割造成p2.equals(p2)的比较实际上是:“张三
  ”.equals(“张三”),一个有空格,一个没有空格就出错了。

    这个违背了equals的自反性原则:对于任何非空引用x,x.equals(x)应该返回true。

    这里只要去掉trim方法就可以解决。

2、对称性原则

    上面这个例子,还并不是很好,如果我们传入null值,会怎么样呢?增加一条语句:Person p2=new Person(null);

结果:
是否包含张三:true
Exception in thread "main" java.lang.NullPointerException


原因在执行p2.equals(p1)时,由于p2的name是一个null值,所以调用name.equalsIgnoreCase()方法时就会报空指针异常。

这是在覆写equals方法时没有遵循对称性原则:对于任何应用x,y的情形,如果想x.equals(y)返回true,那么y.equals(x),也应该返回true。

应该在equals方法里加上是否为null值的判断:
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person person= (Person) obj;
if (person.getName() == null || name == null) {
return false;
}else{
return name.equalsIgnoreCase(person.getName());
}
}
return false;
}


3、传递性原则  

现在我们有一个Employee类继承自person类:
public class Employee extends Person{
private int id;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Employee(String name,int id) {
super(name);
this.id = id;
// TODO Auto-generated constructor stub
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Employee){
Employee e = (Employee)obj;
return super.equals(obj) && e.getId() == id;
}
return super.equals(obj);
}

public static void main(String[] args){
Employee e1=new Employee("张三",12);
Employee e2=new Employee("张三",123);
Person p1 = new Person("张三");

System.out.println(p1.equals(e1));
System.out.println(p1.equals(e2));
System.out.println(e1.equals(e2));
}
}


只有在name和ID都相同的情况下才是同一个员工,避免同名同姓的。在main里定义了,两个员工和一个社会闲杂人员,虽然同名同姓但肯定不是同一个人。运行结果应该三个都是false才对。但是:

true

true

false

    p1尽然等于e1,也等于e2,不是同一个类的实例也相等了?因为p1.equals(e1)是调用父类的equals方法进行判断的它使用instanceof关键字检查e1是否是person的实例,由于employee和person是继承关系,结果就是true了。但是反过来就不成立,e1,e2就不等于p1,这也是违反对称性原则的一个典型案例。
 e1竟然不等于e2?e1.equals(e2)调用的是Employee的equals方法,不仅要判断姓名相同还有判断工号相同,两者的工号不同,不相等是对的。但是p1等于e1,也等于e2,e1却不等于e2,这里就存在矛盾,等式不传递是因为违反了equals的传递性原则:对于实例对象x、y、z;如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。

上述情况会发生是因为父类使用instanceof关键字(是否是这个特定类或者是它的子类的一个实例),用来判断是否是一个类的实例对象的,这很容易让子类“钻空子”。想要解决也很简单,使用getClass进行类型的判断,person类的equals方法修改如下:

@Override
public boolean equals(Object obj) {
if (obj != null && obj.getClass() == this.getClass()) {
Person person= (Person) obj;
if (person.getName() == null || name == null) {
return false;
}else{
return name.equalsIgnoreCase(person.getName());
}
}
return false;
}

当然,考虑到Employee也有可能被继承,也需要把它的instanceOf修改为getClass。总之,在覆写equals时建议使用getclass进行类型判断,而不要使用instanceof。

4、必须覆写hashCode方法这样结果就是三个false。

覆写equals方法就必须覆写hashCode方法,这条规则基本上每个Javaer都知道的,也是JDK API上反复说明的。

public static void main(String[] args){

//Person类的实例作为Map的key

Map<Person, Object> map = new HashMap<Person, Object>{

{put(new Person("张三", new Object());}

}

//Person类的实例作为List的元素

List<Person> list = new ArrayList<Person>{

{add(new Person("张三");}

}

//列表中是否包含

boolean b1 = list.contains(new Person("张三"));

//Map中是否包含

boolean b2 = map.containsKey(new Person("张三"));

}
代码中的Person类与上一个相同,equals方法完美无缺。在这段代码中,我们在声明时直接调用方法赋值,这其实也是一个内部匿名类的操作。现在的问题是b1和b2这两个boolean值是否都为true?

我们先来看b1,Person类的equals覆写了,不再判断两个地址是否相等,而是根据人员的姓名来判断两个对象是否相等,所以不管我们的new Person(“张三”)产生了多少个对象,它们都是相等的。把“张三”对象放入list中,再检查list中是否包含,那结果肯定是true了。

接着来看b2,我们把张三这个对象作为了Map的键(Key),放进去的对象时张三,检查的对象还是张三,那应该和List的结果相同了,但是很遗憾,结果是false。原因何在呢?

原因就是HashMap的底层处理机制是以数组的方式保存map条目(Map Entry)的,这其中的关键是这个数组下标的处理机制:依据传入元素的hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有了map条目,且与传入的键值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到map条目的链表中。同理检查键是否存在也是根据哈希码确定文职,然后遍历查找键值的。

那么对象元素的hashCode方法返回的是什么值呢?它是一个对象的哈希码,是由Object类的本地方法生成的,确保每个对象有一个哈希码(这也是哈希算法的基本要求:任意输入k,通过一定算法f(k),将其转换为非可逆的输出,对于两个输入k1和k2,要求若k1=k2,则必须f(k1)= f(k2),但不允许k1≠k2,f(k1)=f(k2)的情况存在)。

回到我们的例子上,由于我们没有重写hashcode方法,两个张三对象的hashcode方法返回值(也就是哈希码)肯定是不相同的,在HashMap的数组中也就找不到对应的Map条目了,于是就返回了false。

问题清楚了,修改也非常简单,重写一下hashcode方法即可,代码如下:

class Person{

/*其他代码相同,不再赘述*/

@Override

public int hashCode(){

return new HashCodeBuilder().append(name).toHashCode();

}

}


其中HashCodeBuilder是org.apache.commons.lang.builder包下的一个哈希码生成工具,使用起来非常方便,诸位可以直接在项目中集成。

感谢大神的分享,具体可参照:http://blog.csdn.net/lonely_fireworks/article/details/7974307
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  equals方法