黑马程序员-java笔记之equals和hascode方法总结。

时间:2021-12-16 16:13:27

------- android培训java培训、期待与您交流! ----------

1.equals()和==操作符

先谈谈equals()方法和==操作符吧。有一个Book类,如下代码:

public class Book {

private String name;
private int price;

public Book(String name, int price) {
super();
this.name = name;
this.price = price;
}
}

当用equal和==比较两个Book的实例对象时,均为false:

Book book = new Book("java", 11);
Book book1 = new Book("java", 11);
System.out.println(book == book1); //false
System.out.println(book.equals(book1));//false

这是内存布局图:

黑马程序员-java笔记之equals和hascode方法总结。

new出book1和book两个Book的实例对象时,内存布局如上图,book和book1两个变量的值均在栈内存中分配,其存储的是两个book对象的内存地址。

使用==比较两个对象:其实是在比较两个对象的引用的值。

book == book1实际上是在比较0X00F1 == 0X00F2是否相等,答案肯定是不等的。用==在比较时,有如下规则:

1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
他们之间的比较,应用双等号(==),比较的是他们的值。
2.复合数据类型(类)
当他们用(==)进行比较的时候,比较的是他们在内存中的存放地址,所以,除非是同一个new出来的对象,他们的比较后的结果为true,否则比较后结果为false。


使用equals比较两个对象:结果也为false,equals方法继承自Object类,其内容如下:

    public boolean equals(Object obj) {
return (this == obj);
}
从这个方法中可以看出,只有当一个实例等于它本身的时候,equals()才会返回true值。通俗地说,此时比较的是两个引用是否指向内存中的同一个对象,也可以称做是否实例相等。而我们在使用equals()来比较两个指向值对象的引用的时候,往往希望知道它们逻辑上是否相等,而不是它们是否指向同一个对象——所以在此我们要重写equals方法,使得两个逻辑上相等的Book对象在比较时相等。

要重写equals方法必须遵守重写规范,如果不遵守,则会导致很多错误,

下面是约定的内容,来自java.lang.Object的规范,equals方法实现了等价关系,以下是要求遵循的5点

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,如果用于equals比较的对象信息没有被修改,多次调用x.equals(y)要么一致地返回true,要么一致地返回false。

5. 非空性:对于任意的非空引用值x,x.equals(null)一定返回false。

 重写equals方法后最好重写hashCode方法,否则两个等价对象可能得到不同的hashCode,这在集合框架中使用可能产生严重后果。所以除了要遵守以上五点以外,还要同时重写hascode方法。

2.hashcode方法

 定义:public native int hashCode(); 

说明是一个本地方法,它的实现是根据本地机器相关的。再看它比较“官方”的详细说明:hashCode()返回该对象的哈希码值,该值通常是一个由该对象的内部地址转换而来的整数,它的实现主要是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。
 
在每个重写了equals方法的类中,你必须也要重写hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值(hash)的集合类结合在一起正常运行。
hashCode()的返回值和equals()的关系如下:
如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。
如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

3.重写equals()和hashcode()

为了使对象能够在逻辑上相等时equals方法能够返回true,我们来重写equals方法。

@Override
public boolean equals(Object obj) {
//1. 自反性:对于任意的引用值x,x.equals(x)一定为true。
if (this == obj)
return true;
//如果目标对象为空,则为false
if (obj == null)
return false;
//如果两个对象不属于同一个类,则返回true
if (getClass() != obj.getClass())
return false;
//将Object强转为Book
Book other = (Book) obj;
//对Book的两个属性进行比较,如果都完全相等,则返回true.
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (price != other.price)
return false;
return true;
}

同时要重写hashcode方法:

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + price;
return result;
}

在HashSet中测试重写的正确性:

Book book = new Book("java", 11);
Book book1 = new Book("java", 11);
HashSet<Book> set = new HashSet<Book>();
set.add(book);
set.add(book1);
System.out.println(set.size());

HashSet中的存储时,会首先判断两个对象的hashcode值是否相等, 如果不相等,直接判断为不同的对象,就添加进去了,如果相等,再用equals方法进一步判断两个对象是否相等。

如没有重写equals方法和hashcode方法,以上代码的返回值是2,因为两个对象equals不等,所以HashSet把这两个对象当做不同的对象来处理,都会添加进去。

如果重写了equals方法而没有重写hashcode方法,返回值也是2,因为他们的hashcode方法可能不相等,HashSet直接就认为这两个对象不等,不进行equals判断了。

只有重写了equals方法和hashcode方法,返回值才为1,HashSet先进行hashcode判断,再进行equals判断,发现两个对象相等,认为这两个对象相等,就不会添加了。

特别注意:如果重写了这两个方法,在往HashSet中添加完值以后,就不能修改参与hash运算的字段了,因为一旦修改,由于HashSet是根据hash值来存储和获取对象的,我们将获取不到这个对象,会造成内存泄露。