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学习者来到我的评论区与我一同分享讨论,我们一起进步学习!加油!

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信