HashSet储存对象时重写equals和hashcode方法时的情景分析

Set 接口的特点:

  • 1.它不允许出现重复元素-----------无重复

  • 2.不保证集合中元素的顺序---------无序

  • 3.允许包含值为null的元素,但最多只能有一个null元素。

hashCode() 与 equals()

HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于:hashCode()与equals()方法

  1. hashCode() 方法:

Returns a hash code value for the object. This method issupported for the benefit of hash tables such as those provided by java.util.HashMap.

当向hash集合(诸如HashSet、HashMap等)中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的hashCode值,然后根据 hashCode值 决定该对象在 HashSet 中的存储位置。

  1. equals() 方法:

true if this object is the same as the objargument; false otherwise

两个元素通过equals()方法比较内容1是否相同。

hash集合判断标准

两个对象通过equals() 方法比较相等,并且两个对象的hashCode() 方法返回值也相等

hashset不能为一样的,放入一个值首先判断hashcode(内存中的位置)是否已经存在,然后用equals判断是否有一样的值。

关于hash集合:如果两个元素通过equals()方法比较返回true,但是这两个元素的hashcode()返回方法不等,hashset会把他们存储在不同的位置,依然可以添加成功。这也就是为什么“当把对象存储在集合中时,需要重写该类的equals()方法与hashCode()方法的原因”

两种情形

情形一:当我们往HashSet集合中添加 8大基本类型和String类型的时候,不需要重写hashCode()和equals()方法。因为任何对象都是Object类的子类,所以任何对象都拥有这个方法。

情形二:当我们往HashSet集合添加自定义对象的时候,就需要重写 hashCode() 和 equals() 方法。建立自己的比较方式,才能保证HashSet集合中的对象唯一。

情形一示例:

HashSet 集合中添加 8大基本类型和String类型的时候,不需要重写hashCode() 和equals() 方法

package Packger;

import java.util.ArrayList;

public class TestHashSet {
	public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        arrayList.add(12);
        arrayList.add(10);
        arrayList.add(35);
        arrayList.add(100);
        System.out.println("元素为:");
        System.out.println(arrayList);
        }
}

输出结果为:

元素为:
[12, 10, 35, 100]
情形二示例:

情形二: HashSet集合添加自定义对象的时候,就需要重写 hashCode() 和 equals() 方法。

  1. 自定义对象(不进行重写)

Student01.java

package container;
/**
 *  Created by Haoweixl on 2021/1/31.
 */
public class Student01 {
	  private String number;//学号
	  private String name;   //姓名
	  private Integer age;    //年龄
	
	  public Student01(String number, String name, Integer age) {
		super();
		this.number = number;
		this.name = name;
		this.age = age;
	  }

	public String getNumber() {
		return number;
	}

	public void setNumber(String number) {
		this.number = number;
	}

	public String getName() {
		return name;
	}

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

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "Student01 [number=" + number + ", name=" + name + ", age=" + age + "]";
	}
}
  1. 测试一:测试自定义对象(不进行重写):

Test01.java

package container;
/**
 *  Created by Haoweixl on 2021/1/31.
 */
import java.util.HashSet;

public class Test01 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashSet<Student01> set = new HashSet<>();
		Student01 student1 = new Student01("123", "name1", 11);
		Student01 student2 = new Student01("123", "name1", 11);
		set.add(student1);
		set.add(student2);
		System.out.println("Hashset 元素:"+set);// 默认调用 toString()方法
		System.out.println("集合元素总数:"+set.size());
	}
}

输出结果:

Hashset 元素:[Student01 [number=123, name=name1, age=11], Student01 [number=123, name=name1, age=11]]
集合元素总数:2

在这里插入图片描述

  1. 重写 hashCode() 与 equals() 的自定义对象 (使用eclipse自动重写)

Set 集合有去重的功能,但是在向 Set 集合中添加自定义的对象时无法去重,我们重写一下 Student 类的 equals() 和 hashCode() 方法(此处的 equals() 和 hashCode() 使用 eclipse 自动生成的)。

Student02.java

package container;

public class Student02 {
	private String number;//学号
	private String name;   //姓名
	private Integer age;    //年龄
	
	public Student02(String number, String name, Integer age) {
		super();
		this.number = number;
		this.name = name;
		this.age = age;
	}

	public String getNumber() {
		return number;
	}

	public void setNumber(String number) {
		this.number = number;
	}

	public String getName() {
		return name;
	}

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

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public int hashCode() {
		final int prime = 31;//获取学号、姓名、年龄的 hashCode(即所在hash表的索引) 分别乘以 31
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((number == null) ? 0 : number.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) //判断传入的是否为同一个对象
			return true;
		if (obj == null)//判断传入的对象是否为空
			return false;
		if (getClass() != obj.getClass())//判断传入的对象是不是相同的类
			return false;
		Student02 other = (Student02) obj;//强转
		if (age == null) {//判断年龄是否相同
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) { //判断姓名是否相同
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (number == null) {//判断学号是否相同
			if (other.number != null)
				return false;
		} else if (!number.equals(other.number))
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Student02 [number=" + number + ", name=" + name + ", age=" + age + "]";
	}
}
  1. 测试二:测试重写后的自定义对象

Test02.java:

package container;
/**
 *  Created by Haoweixl on 2021/1/31.
 */
import java.util.HashSet;

public class Test02 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		HashSet<Student02> set2 = new HashSet<>();
		Student02 student3 = new Student02("123", "name1", 11);
		Student02 student4 = new Student02("123", "name1", 11);
		set2.add(student3);
		set2.add(student4);
		System.out.println("Hashset 元素:"+set2);// 默认调用 toString()方法
		System.out.println("集合元素总数:"+set2.size());
	}

}

显示结果:

Hashset 元素:[Student02 [number=123, name=name1, age=11]]
集合元素总数:1

在这里插入图片描述

HashSet 源码分析

HashSet的 add 方法实际上调用的是 HashMap 的put 方法,我们来看看 HashMap 的 put 方法。

HashMap put 方法:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

HashMap hash方法:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

调用 put 方法时,会调用相应的自定义对象的 hashCode 方法,来获取该对象在 Node 数组中的坐标。因此我们在 User 对象中重写 hashCode 方法是确保相同的对象在同一个 Node 数组中会有相同的坐标。

在储存对象时当发生冲突的时候会经过下面一段代码:

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))

这段代码首先比较两个对象是不是同一个对象,然后通过 equals 方法判断对象的属性值是否相同,来确认是否为同一个对象。

看看下面的测试用例:

Test03:

import java.util.HashSet;

/**
 * Created by MGL on 2017/5/4.
 */
public class Test03 {
    public static void main(String[] args) {

        HashSet<User> set = new HashSet<>();
        User user1 = new User("123", "zhangsan", 11);
        set.add(user1);
        set.add(user1);
        System.out.println(set.size());
    }
}

其实这两段代码的运行结果是一样的,但是它们的内部运行原理是不相同的,在 Test02 中向 Set 集合中添加了两个不同对象,两个对象的属性值相同,在 Test03 中也是向 Set 集合中添加了两个对象,但是是同一个对象。到底有什么不同,我们在 User 类的 equals 方法出打个断点,调试一下,我们可以发现一个有趣的现象,Test02 可以调用到 equals 而 Test03 不会调用到 equals 这个方法,这到底发生了什么???

其实问题的关键还是在于这一行代码:

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))

当我们储存同一个对象的时候 (k = p.key) == key 计算结果为 true ,运用双或 的特点,直接就返回了 true ,而 Test03 不是传入的同一个对象,因此会调用 key.equals(k) 这段代码,调用自定义类的 equals 方法。

总结

1.我们在使用 HashSet 对自定义类进行去重的时候,一定要覆盖自定义类的 equals 和 hashCode 方法,hashCode 方法是找到当前对象在 Node 数组重的位置,而 equals 是比较当前对象与对应坐标链表中的对象是否相同。

2.在使用Set对象储存自定义对象的时候,每次都会调用自定义对象的 hashCode 方法,但是 equals 方法并不是每次都会被调用到,不会被调用到的情形有下:

  1. 传入同一个对象;
  2. 当前坐标为空;

3.要根据自己要实现的功能,合理的重写 hashCode 和 equals 方法来达到去重的目的。

参考博客:

https://www.cnblogs.com/lwhsummer/p/11129696.html

https://blog.csdn.net/mgl934973491/article/details/71169659

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

何为xl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值