hello,大家好!最近小编我在重温Set集合中学会了很多,尤其是对于HashSet的去重?初学java的时候对于hashSet也只是一比带过只知道他可以去重但又不知其背后的原理,而面对将个属性值相同的对象时用hashSet依旧不能消除重复的问题也只是(以他们在地址值不一样的答案简单说服自己)。但是,在现实中我们则是以属性一样的对象为同一个对象。就好比我们身份证的ID属性,如果我们将相同的身份证ID输入电脑,电脑却以地址值不一样而判断他们是不同的人,那我们辛辛苦苦研发计算机如果是这样的话,那小编我可要说了:“要你有何用”?
下面我将以代码和运行结果的方式带你了解HashSet的去重原理
1. 当集合类型为基本数据类型或者String类型的时候,Hashset的去重可以成功实现有以下代码可以得知,现在我先建一个集合类型为String类型的HashSet集合来进行实验:
package SetList;
import java.util.HashSet;
import java.util.Set;
public class DemoTest1 {
public static void main(String[] args) {
Set<String> set =new HashSet<String>();//创建一个String类型的集合
boolean s1 = set.add("a"); //添加两个相同的元素,并用boolean类型进行检验
boolean s2=set.add("a"); //再添加一个相同元素
boolean s3= set.add("b"); //添加一个不值的元素
System.out.println("s1的boolean值为:"+s1); //输出s1
System.out.println("s2的boolean值为:"+s2); //输出s2
System.out.println("s3的boolean值为:"+s3); //输出s3
System.out.println("整个set集合的值为:"+set); //输出整个集合
}
}
运行结果如下:
我们可以明显的看出s2的值为false说明a没有加入到set集合当中,但是s3的值为true说明不同于a的b却可以成功的加入到集合当中。最终的set值也只有两个,说明HashSet的去重效果显著。
接下来我们试试自定义对象再来看看:
2.在另一个包建立一个Person类:
package bean;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
再建立一个类新建一个为类型为Person的Set集合:
package SetList;
import java.util.HashSet;
import bean.Person;
public class DemoTest2 {
public static void main(String[] args) {
HashSet<Person> he = new HashSet<>();//对象类集合
he.add(new Person("张三",23)); //在hs集合中添加Person类对象
he.add(new Person("张三",23)); //重复添加
he.add(new Person("李四",24)); //添加李四
he.add(new Person("李四",24));
he.add(new Person("李四",24));
he.add(new Person("李四",24));
for (Person person : he) { //遍历该集合
System.out.println(person);
}
}
}
运行结果如下 :
看到没,HashSet去重失败!来个简单的理由说服自己吧:“每个对象在内存的地址值不一样!所以被认为是不同的对象。”那接下来我们就在Person类中重写个equals()方法一一进行比对看看呗
package bean;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
System.out.println("我执行了吗");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age ==p.age;
}
}
再看看运行台
依然毫无波澜,equals()方法也没有成功执行,既然是HashSet那么怎么能少了个HashCode()方法呢?来,加上:
package bean;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object obj) {
System.out.println("我执行了吗");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age ==p.age;
}
@Override
public int hashCode() {//我给加上hashCode()方法
return super.hashCode();
}
}
再次转向运行台,依旧毫无波澜(太失望了!!!)那就再改改吧
我返回一个10看看
@Override
public boolean equals(Object obj) {
System.out.println("我执行了吗");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age ==p.age;
}
@Override
public int hashCode() {//我给加上hashCode()方法
return 10;
}
//前面的代码和上几张图一样
再来看看运行台:
这次终于执行,原来小小的10竟然有如此魅力勾住我equals方法!
为什么呢?
爱肝代码的小编我特意去学习了相关的知识,原来在HashSet集合中,当输入两个相同属性值的对象时,程序是先经过HashCode()方法之后再去执行equals()方法的,而如果没有重写HashCode()方法的话则默认返回对象的地址值,当然两个对象的地址值是不同的,也就没有继续执行equals方法继续进一步的对象比对。当返回的值相同时就进行下一步的比对equals()方法比对。但是虽然说代码equals()方法成功执行并输出出了正确的结果,但是输出的多条“我执行了吗”可见执行了很多次该方法。
我们知道如果在集合中我们添加了很多个对象,而我们统一给定一个hashcode的返回值的话,就都会去执行equals()方法,那么效率就特别慢。那就再改一下吧:
@Override
public boolean equals(Object obj) {
System.out.println("我执行了吗");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age ==p.age;
}
@Override
public int hashCode() {//我给加上hashCode()方法
return age;
}
//前面的代码和上几张图一样
再次运行:
我们可以明显感觉到执行的equals()方法次数更少了一些,但是细心的朋友可以发现如果年龄的值都是24的话那么,equals()方法执行的次数依旧很多,因此返回age依旧是一个不好的法子。我们就要使HashCode方法返回的值尽可能的不同,接下来试试这个
@Override
public boolean equals(Object obj) {
System.out.println("我执行了吗");
Person p = (Person)obj;
return this.name.equals(p.name) && this.age ==p.age;
}
//让hashcode的值尽量不重复
@Override
public int hashCode() {
final int NUM = 38;
return name.hashCode() * NUM +age;
}
这种方法进一步的使hashcode的值不一样,从而进一步的优化了代码。但是当数如繁星的对象进入集合当中,这种方法依旧是效率低。没办法,我们看看eclipse官方提供的方法吧:(欢迎来到我的详解代码)
package bean;
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
/以下方法的执行顺序为:先执行hashcode方法再执行equals方法,当hashcode的值为一样的时候才会执行equals方法
@Override
public int hashCode() {//尽量是hashcode的值不一样
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
* 为什么是31?
* 1.31是一个质数(能够被一整除的数)
* 2.31这个数既不大也不小(如果太大就会超过int的取值范围)(太小的话就有可能重复)
* 3.31这个数好算,2的5次方减1,2向左移动5位
*/
@Override
public boolean equals(Object obj) {
if (this == obj) //调用的对象和传入的对象是同一个对象
return true; //直接返回true
if (obj == null) //传入对象为null
return false; //返回false
if (getClass() != obj.getClass()) //判断两个对象的字节码文件是否是同一个字节码(去除类型强转异常)
return false; //如果不是直接返回false
Person other = (Person) obj; //向下转型
if (age != other.age) //调用对象的年龄不等于传入对象的年龄
return false; //返回false
if (name == null) { //调用对象的姓名为null
if (other.name != null) // 传入对象的姓名不为null
return false; //返回false
} else if (!name.equals(other.name)) //调用对象的姓名不等于传入对象的姓名
return false; //返回false
return true; //返回true
}
}
总结:
HashSet的原理:
当我们使用Set集合时,都是去用它去除里面的重复元素,假如我们在存储的时候对于那些对象一一进行equals()方法进行比较的话,这效率可就太低了(耗时间又耗内存),而使用hash算法的HashCode()方法,则提高了去重的效率,也使equals()方法的执行次数大大降低!
1.当我们的HashSet调用add()方法去存储对象的时候,是先调用对象的HashCode()得到一个哈希值,然后在集找是否有哈希值相同的对象。
(1)、如果没有哈希值相同的对象就直接加入到集合当中不用再经过equals()方法进行比对
(2)、如果哈希值相同的对象的话,就进一步用equals()方法比对去重。
2. 将我们自定义的对象存入set集合当中时:
【1】、类中必须重写equals()和HashSet()这两个方法
【2】、HashCode():属性相同的对象返回值必须是相同的,属性不相同的对象返回值一定不同
【3】、equals():属性相同就返回true,属性不同就返回false;(此方法是判断是否为同一个对象终极审判)
最后,小编谢谢你能够耐心地看完这篇文章。同时欢迎更多的java学习者来到我的评论区与我一同分享讨论,我们一起进步学习!加油!