Java HashCode 和 equal 详解

Java HashCode 和 equal 详解

目录

Java HashCode 和 equal 详解

1、概述

2、Object源码理解

3、需要重写equals()的场景

4、需要重写hashcode()的场景

5、原理分析

6、总结


1、概述

我们都知道,要比较两个对象是否相等时需要调用对象的equals()方法,即判断对象引用所指向的对象地址是否相等,对象地址相等时,那么与对象相关的对象句柄、对象头、对象实例数据、对象类型数据等也是完全一致的,所以我们可以通过比较对象的地址来判断是否相等。

默认的hashcode就是根据对象地址生成的一个整数值。

img

扫描二维码关注公众号,回复: 10921007 查看本文章

2、Object源码理解

对象在不重写的情况下使用的是Object的equals方法和hashcode方法,从Object类的源码我们知道,默认的equals 判断的是两个对象的引用指向的是不是同一个对象;而hashcode也是根据对象地址生成一个整数数值

另外我们可以看到Object的hashcode()方法的修饰符为native,表明该方法是否操作系统实现,java调用操作系统底层代码获取哈希值。

public class Object {
 
    public native int hashCode();
 
    
    public boolean equals(Object obj) {
        return (this == obj);
    }
    ..........
    ..........
}

3、需要重写equals()的场景

假设现在有很多学生对象,默认情况下,要判断多个学生对象是否相等,需要根据地址判断,若对象地址相等,那么对象的实例数据一定是一样的,但现在我们规定:当学生的姓名、年龄、性别相等时,认为学生对象是相等的,不一定需要对象地址完全相同,例如学生A对象所在地址为100,学生A的个人信息为(姓名:A,性别:女,年龄:18,住址:北京软件路999号,体重:48),学生A对象所在地址为388,学生A的个人信息为(姓名:A,性别:女,年龄:18,住址:广州暴富路888号,体重:55),这时候如果不重写Object的equals方法,那么返回的一定是false不相等,这个时候就需要我们根据自己的需求重写equals()方法了。


public class Student {
	private String name;// 姓名
	private String sex;// 性别
	private String age;// 年龄
	private float weight;// 体重
	private String addr;// 地址
	
	// 重写hashcode方法
	@Override
	public int hashCode() {
		int result = name.hashCode();
		result = 17 * result + sex.hashCode();
		result = 17 * result + age.hashCode();
		return result;
	}
 
	// 重写equals方法
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof Student)) {
       // instanceof 已经处理了obj = null的情况
			return false;
		}
		Student stuObj = (Student) obj;
		// 地址相等
		if (this == stuObj) {
			return true;
		}
		// 如果两个对象姓名、年龄、性别相等,我们认为两个对象相等
		if (stuObj.name.equals(this.name) && stuObj.sex.equals(this.sex) && stuObj.age.equals(this.age)) {
			return true;
		} else {
			return false;
		}
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getSex() {
		return sex;
	}
 
	public void setSex(String sex) {
		this.sex = sex;
	}
 
	public String getAge() {
		return age;
	}
 
	public void setAge(String age) {
		this.age = age;
	}
 
	public float getWeight() {
		return weight;
	}
 
	public void setWeight(float weight) {
		this.weight = weight;
	}
 
	public String getAddr() {
		return addr;
	}
 
	public void setAddr(String addr) {
		this.addr = addr;
	}
 
}

现在我们写个例子测试下结果:

public static void main(String[] args) {
	Student s1 =new Student();
	s1.setAddr("1111");
	s1.setAge("20");
	s1.setName("allan");
	s1.setSex("male");
	s1.setWeight(60f);
	Student s2 =new Student();
	s2.setAddr("222");
	s2.setAge("20");
	s2.setName("allan");
	s2.setSex("male");
	s2.setWeight(70f);
	if(s1.equals(s2)) {
		System.out.println("s1==s2");
	}else {
		System.out.println("s1 != s2");
	}
}

在重写了student的equals方法后,这里会输出s1 == s2,实现了我们的需求,如果没有重写equals方法,那么上段代码必定输出s1!=s2。

通过上面的例子,你是不是会想,不是说要同时重写Object的equals方法和hashcode方法吗?那上面的例子怎么才只用到equals方法呢,hashcode方法没有体现出来,不要着急,我们往下看。


4、需要重写hashcode()的场景

  以上面例子为基础,即student1和student2在重写equals方法后被认为是相等的。

         在两个对象equals的情况下进行把他们分别放入Map和Set中

         在上面的代码基础上追加如下代码:

Set set = new HashSet();
	set.add(s1);
	set.add(s2);
	System.out.println(set);

如果没有重写Object的hashcode()方法(即去掉上面student类中hashcode方法块),这里会输出

[jianlejun.study.Student@7852e922, jianlejun.study.Student@4e25154f]

 说明该Set容器类有2个元素。.........等等,为什么会有2个元素????刚才经过测试,s1不是已经等于s2了吗,那按照Set容器的特性会有一个去重操作,那为什么现在会有2个元素。这就涉及到Set的底层实现问题了,这里简单介绍下就是HashSet的底层是通过HashMap实现的,最终比较set容器内元素是否相等是通过比较对象的hashcode来判断的。现在你可以试试吧刚才注释掉的hashcode方法弄回去,然后重新运行,看是不是很神奇的就只输出一个元素了
 

@Override
	public int hashCode() {
		int result = name.hashCode();
		result = 17 * result + sex.hashCode();
		result = 17 * result + age.hashCode();
		return result;
	}

或许你会有一个疑问?hashcode里的代码该怎么理解?该如何写?其实有个相对固定的写法,先整理出你判断对象相等的属性,然后取一个尽可能小的正整数(尽可能小时怕最终得到的结果超出了整型int的取数范围),这里我取了17,(好像在JDK源码中哪里看过用的是17),然后计算17*属性的hashcode+其他属性的hashcode,重复步骤。

重写hashcode方法后输出的结果为:
 

[jianlejun.study.Student@43c2ce69]

同理,可以测试下放入HashMap中,key为<s1,s1>,<s2,s2>,Map也把两个同样的对象当成了不同的Key(Map的Key是不允许重复的,相同Key会覆盖)那么没有重写的情况下map中也会有2个元素,重写的情况会最后put进的元素会覆盖前面的value

Map m = new HashMap();
	m.put(s1, s1);
	m.put(s2, s2);
	System.out.println(m);
	System.out.println(((Student)m.get(s1)).getAddr());
 
输出结果:
{jianlejun.study.Student@43c2ce69=jianlejun.study.Student@43c2ce69}
222

可以看到最终输出的地址信息为222,222是s2成员变量addr的值,很明天,s2已经替换了map中key为s1的value值,最终的结果是map<s1,s2>。即key为s1value为s2.

5、原理分析

因为我们没有重写父类(Object)的hashcode方法,Object的hashcode方法会根据两个对象的地址生成对相应的hashcode;

s1和s2是分别new出来的,那么他们的地址肯定是不一样的,自然hashcode值也会不一样。

Set区别对象是不是唯一的标准是,两个对象hashcode是不是一样,再判定两个对象是否equals;

Map 是先根据Key值的hashcode分配和获取对象保存数组下标的,然后再根据equals区分唯一值
 

6、总结

(1)默认的 equal方法比较的是两个对象的引用地址是否相同;而默认 hashcode 的值由对象地址的决定的。

(2)为什么重写equal 方法,也要重写hashcode 方法?

         主要是对于集合情况下,需要保证对象的一致性;如set集合,它是首先通过hashcode的值找到相应的位置,然后再通过equal比较两个对象是否相同。例如,我们认为两个学生对象,只要学号跟姓名相同,就认为两个对象是相同的;如果我们只重写equal方法,那么new 两个对象时,尽管他们的属性是相同的,但他们的hashcode是不相同的,存在set集合中便有两个对象。如果重写了hashcode,那么在集合中就只有一个对象。

(3)== 和 equal 的区别:

        1、== 情况下:

       基本类型:比较的就是值是否相同

  引用类型:比较的就是地址值是否相同

        2、equal情况下 :

       默认情况下是比较地址是否相同。

       但如果重写了equal方法,如String,Integert类,那么比较的就是按照equal的逻辑比较,如String 比较的是值。

参考:

详细可以点击这里

发布了78 篇原创文章 · 获赞 53 · 访问量 8140

猜你喜欢

转载自blog.csdn.net/qqq3117004957/article/details/105235880