前面部分摘自:https://blog.csdn.net/javazejian/article/details/51348320

一:Object中equals方法的实现原理

public boolean equals(Object obj) {
return (this == obj);
}

每个对象都有内存地址和数据,“==”比较2个对象的地址,Object类中的equals()比较的是2个对象的地址是否相同,object1.equals(object2) 为 true,则表示 equals1 和 equals2 实际上是引用同一个对象。

二:equals与“==”的区别

equals比较的是内容,“==”比较的是地址。

很明显,这个说法是不完全的

来看个例子

package com.zejian.test;
public class Car {
private int batch;
public Car(int batch) {
this.batch = batch;
}
public static void main(String[] args) {
Car c1 = new Car(1);
Car c2 = new Car(1);
System.out.println(c1.equals(c2));
System.out.println(c1 == c2);
}
}

console

false

false

分析:“==”比较的是地址,所以是false,这个不用多说.但是为什么equals也是false呢?因为所以类都集成Object类,Car类没有重写Object类里面的equals方法,所以是使用父类的equals方法,return (this == obj); 比较的亦是对象的地址,那么怎么解决这个问题呢?

那就是重写equals方法(比较内容)

@Override
public boolean equals(Object obj) {
if (obj instanceof Car) {
Car = (Car) obj;
return batch == c.batch;
}
return false;
}

三:equals的重写规则

自反性。对于任何非null的引用值x,x.equals(x)应返回true。

对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才返回true。

传递性。对于任何非null的引用值x、y与z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也应返回true。

一致性。对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false。

对于任何非空引用值x,x.equal(null)应返回false。

package com.zejian.test;
public class Car {
private int batch;
public Car(int batch) {
this.batch = batch;
}
public static void main(String[] args) {
Car c1 = new Car(1);
Car c2 = new Car(1);
Car c3 = new Car(1);
System.out.println("自反性->c1.equals(c1):" + c1.equals(c1));
System.out.println("对称性:");
System.out.println(c1.equals(c2));
System.out.println(c2.equals(c1));
System.out.println("传递性:");
System.out.println(c1.equals(c2));
System.out.println(c2.equals(c3));
System.out.println(c1.equals(c3));
System.out.println("一致性:");
for (int i = 0; i < 50; i++) {
if (c1.equals(c2) != c1.equals(c2)) {
System.out.println("equals方法没有遵守一致性!");
break;
}
}
System.out.println("equals方法遵守一致性!");
System.out.println("与null比较:");
System.out.println(c1.equals(null));
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Car) {
Car c = (Car) obj;
return batch == c.batch;
}
return false;
}
}
自反性->c1.equals(c1):true

对称性:

true

true

传递性:

true

true

true

一致性:

equals方法遵守一致性!

与null比较:

false

子类与父类混合比较

package com.zejian.test;
public class BigCar extends Car {
int count;
public BigCar(int batch, int count) {
super(batch);
this.count = count;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BigCar) {
BigCar bc = (BigCar) obj;
return super.equals(bc) && count == bc.count;
}
return false;
}
public static void main(String[] args) {
Car c = new Car(1);
BigCar bc = new BigCar(1, 20);
System.out.println(c.equals(bc));
System.out.println(bc.equals(c));
}
}

console:

true

false

对于这样的结果,自然是我们意料之中的啦。因为BigCar类型肯定是属于Car类型,所以c.equals(bc)肯定为true,对于bc.equals(c)返回false,是因为Car类型并不一定是BigCar类型(Car类还可以有其他子类)。嗯,确实是这样。但如果有这样一个需求,只要BigCar和Car的生产批次一样,我们就认为它们两个是相当的,在这样一种需求的情况下,父类(Car)与子类(BigCar)的混合比较就不符合equals方法对称性特性了。很明显一个返回true,一个返回了false,根据对称性的特性,此时两次比较都应该返回true才对。那么该如何修改才能符合对称性呢?其实造成不符合对称性特性的原因很明显,那就是因为Car类型并不一定是BigCar类型(Car类还可以有其他子类),在这样的情况下(Car instanceof BigCar)永远返回false,因此,我们不应该直接返回false,而应该继续使用父类的equals方法进行比较才行(因为我们的需求是批次相同,两个对象就相等,父类equals方法比较的就是batch是否相同)。因此BigCar的equals方法应该做如下修改:

@Override
public boolean equals(Object obj) {
if (obj instanceof BigCar) {
BigCar bc = (BigCar) obj;
return super.equals(bc) && count == bc.count;
}
return super.equals(obj);
}

这样运行的结果就都为true了。但是到这里问题并没有结束,虽然符合了对称性,却还没符合传递性,实例如下:

package com.zejian.test;
public class BigCar extends Car {
int count;
public BigCar(int batch, int count) {
super(batch);
this.count = count;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BigCar) {
BigCar bc = (BigCar) obj;
return super.equals(bc) && count == bc.count;
}
return super.equals(obj);
}
public static void main(String[] args) {
Car c = new Car(1);
BigCar bc = new BigCar(1, 20);
BigCar bc2 = new BigCar(1, 22);
System.out.println(bc.equals(c));
System.out.println(c.equals(bc2));
System.out.println(bc.equals(bc2));
}
}

console:

true

true

false

bc,bc2,c的批次都是相同的,按我们之前的需求应该是相等,而且也应该符合equals的传递性才对。但是事实上运行结果却不是这样,违背了传递性。出现这种情况根本原因在于:

父类与子类进行混合比较。

子类中声明了新变量,并且在子类equals方法使用了新增的成员变量作为判断对象是否相等的条件。

只要满足上面两个条件,equals方法的传递性便失效了。而且目前并没有直接的方法可以解决这个问题。因此我们在重写equals方法时这一点需要特别注意。虽然没有直接的解决方法,但是间接的解决方案还说有滴,那就是通过组合的方式来代替继承,还有一点要注意的是组合的方式并非真正意义上的解决问题(只是让它们间的比较都返回了false,从而不违背传递性,然而并没有实现我们上面batch相同对象就相等的需求),而是让equals方法满足各种特性的前提下,让代码看起来更加合情合理,代码如下:

package com.zejian.test;
public class Combination4BigCar {
private Car c;
private int count;
public Combination4BigCar(int batch, int count) {
c = new Car(batch);
this.count = count;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Combination4BigCar) {
Combination4BigCar bc = (Combination4BigCar) obj;
return c.equals(bc.c) && count == bc.count;
}
return false;
}
}

四:equals深入解读

前面我们一再强调了equals方法重写必须遵守的规则,接下来我们就是分析一个反面的例子,看看不遵守这些规则到底会造成什么样的后果。

package com.zejian.test;
import java.util.ArrayList;
import java.util.List;
/** * 反面例子 * @author zejian */
public class AbnormalResult {
public static void main(String[] args) {
List<A> list = new ArrayList<A>();
A a = new A();
B b = new B();
list.add(a);
System.out.println("list.contains(a)->" + list.contains(a));
System.out.println("list.contains(b)->" + list.contains(b));
list.clear();
list.add(b);
System.out.println("list.contains(a)->" + list.contains(a));
System.out.println("list.contains(b)->" + list.contains(b));
}
static class A {
@Override
public boolean equals(Object obj) {
return obj instanceof A;
}
}
static class B extends A {
@Override
public boolean equals(Object obj) {
return obj instanceof B;
}
}
}

console:

list.contains(a)->true

list.contains(b)->false

list.contains(a)->true

list.contains(b)->true

19行和24行的输出没什么好说的,将a,b分别加入list中,list中自然会含有a,b。但是为什么20行和23行结果会不一样呢?我们先来看看contains方法内部实现

@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}

进入indexof方法

@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}

可以看出最终调用的是对象的equals方法,所以当调用20行代码list.contains(b)时,实际上调用了

b.equals(a[i]),a[i]是集合中的元素集合中的类型而且为A类型(只添加了a对象),虽然B继承了A,但此时

a[i] instanceof B

结果为false,equals方法也就会返回false;而当调用23行代码list.contains(a)时,实际上调用了a.equal(a[i]),其中a[i]是集合中的元素而且为B类型(只添加了b对象),由于B类型肯定是A类型(B继承了A),所以

a[i] instanceof A

结果为true,equals方法也就会返回true,这就是整个过程。但很明显结果是有问题的,因为我们的 list的泛型是A,而B又继承了A,此时无论加入了a还是b,都属于同种类型,所以无论是contains(a),还是contains(b)都应该返回true才算正常。而最终却出现上面的结果,这就是因为重写equals方法时没遵守对称性原则导致的结果,如果没遵守传递性也同样会造成上述的结果。当然这里的解决方法也比较简单,我们只要将B类的equals方法修改一下就可以了。

static class B extends A{
@Override
public boolean equals(Object obj) {
if(obj instanceof B){
return true;
}
return super.equals(obj);
}
}

到此,我们也应该明白了重写equals必须遵守几点原则的重要性了。当然这里不止是list,只要是java集合类或者java类库中的其他方法,重写equals不遵守5点原则的话,都可能出现意想不到的结果。

五.为什么重写equals()的同时还得重写hashCode()

一个很常见的错误根源在于没有覆盖hashCode方法。在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。

1.在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

2.如果两个对象根据equals()方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。

3.如果两个对象根据equals()方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生相同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。

六、eqauls方法和hashCode方法关系

(1)同一对象上多次调用hashCode()方法,总是返回相同的整型值。

(2)如果a.equals(b),则一定有a.hashCode() 一定等于 b.hashCode()。

(3)如果!a.equals(b),则a.hashCode() 不一定等于 b.hashCode()。此时如果a.hashCode() 总是不等于 b.hashCode(),会提高hashtables的性能。

(4)a.hashCode()==b.hashCode() 则 a.equals(b)可真可假

(5)a.hashCode()!= b.hashCode() 则 a.equals(b)为假。

关于这两个方法的重要规范:

规范1:若重写equals(Object obj)方法,有必要重写hashcode()方法,确保通过equals(Object obj)方法判断结果为true的两个对象具备相等的hashcode()返回值。说得简单点就是:“如果两个对象相同,那么他们的hashcode应该相等”。不过请注意:这个只是规范,如果你非要写一个类让equals(Object obj)返回true而hashcode()返回两个不相等的值,编译和运行都是不会报错的。不过这样违反了Java规范,程序也就埋下了BUG。

规范2:如果equals(Object obj)返回false,即两个对象“不相同”,并不要求对这两个对象调用hashcode()方法得到两个不相同的数。说的简单点就是:“如果两个对象不相同,他们的hashcode可能相同”。

解读equals()和hashCode()的更多相关文章

  1. How to implement equals() and hashCode() methods in Java[reproduced]

    Part I:equals() (javadoc) must define an equivalence relation (it must be reflexive, symmetric, and ...

  2. JAVA中用堆和栈的概念来理解equals() "=="和hashcode()

    在学习java基本数据类型和复杂数据类型的时候,特别是equals()"=="和hashcode()部分时,不是很懂,也停留了很长时间,最后终于有点眉目了. 要理解equals() ...

  3. 关于equals、hashcode和集合类的小结

    一.首先明确一点:equals()方法和hashcode()方法是Object类里的方法. 查看源码可以知道,在Object类中equals(obj)方法直接返回的是  this == obj 的值. ...

  4. Object方法equals、hashCode

    java知识背景: 1)hashCode()方法返回的是Jvm的32位地址 2)==比较的是对象在jvm中的地址 3)Object的equals()比较的就是jvm物理地址 4)比较2个对象使用equ ...

  5. Java中的equals和hashCode方法

    本文转载自:Java中的equals和hashCode方法详解 Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要 ...

  6. Java提高篇——equals()与hashCode()方法详解

    java.lang.Object类中有两个非常重要的方法: 1 2 public boolean equals(Object obj) public int hashCode() Object类是类继 ...

  7. Java中==、equals、hashcode的区别与重写equals以及hashcode方法实例(转)

    Java中==.equals.hashcode的区别与重写equals以及hashcode方法实例  原文地址:http://www.cnblogs.com/luankun0214/p/4421770 ...

  8. java中equals和hashCode方法的解析

    解析Java对象的equals()和hashCode()的使用 前言 在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是自己设计其中一个,就要设计另外一个.在多 ...

  9. Java实战equals()与hashCode()

    一.equals()方法详解 equals()方法在object类中定义如下: 代码 public boolean equals(Object obj) { return (this == obj); ...

随机推荐

  1. csdn token

    http://download.csdn.net/download/pp_haitun/9614126 http://dl.download.csdn.net/down11/20160826/28b9 ...

  2. 闰平年简介及计算过程描述 - Java代码实现

    import java.util.Scanner; /** * @author Shelwin Wei * 分析过程请参照<闰平年简介及计算过程描述>,网址 http://www.cnbl ...

  3. HTTP请求GET和POST的区别

    HTTP请求GET和POST的区别: 1.GET提交,请求的数据会附在URL之后(就是把数据放置在HTTP协议头<request-line>中), 以?分割URL和传输数据,多个参数用&a ...

  4. Spring如何解决循环引用

    概念 什么是循环引用? 故名思义,多个对象形成环路. 有哪几种循环引用? 在Spring中存在如下几种循环引用,一一举例分析一下 注入循环引用(Set注入 注解注入) package c.q.m; i ...

  5. 【JavaScript】彻底明白this在函数中的指向

    一.this,其实可以类比成人 说到this的话,我们在js中主要研究的都是函数中的this,在javascript中,this代表当前行为的执行主体,而context代表的是当前行为执行的的环境(区 ...

  6. 移动端使用rem.js,解决rem.js 行内元素占位问题

    父级元素: letter-spacing: -0.5em;font-size: 0; 子级元素: letter-spacing: normal; display: inline-block; vert ...

  7. devexpress 给GridView添加行号

    先找到 此时间gridView1_CustomDrawRowIndicator private void gridView1_CustomDrawRowIndicator(object sender, ...

  8. 从理论到实践,全方位认识HTTP/2

    前言   为了降低加载时间,相信大多数人都做过如下尝试   - Keep-alive: TCP持久连接,增加了TCP连接的复用性,但只有当上一个请求/响应完全 完成后,client才能发送下一个请求 ...

  9. 一张图带你了解webpack的require.context

    很多人应该像我一样,对于webpack的require.context都是一知半解吧.网上很多关于require.context的使用案例,但是我没找到可以帮助我理解这个知识点的,于是也决定自己来探索 ...

  10. C语言中const的用法总结

          const是一个C语言的关键字,它限定一个变量不允许被改变.使用const在一定程度上可以提高程序的安全性和可靠性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程 ...