Set集合HashSet学习(Set集合学习一)
Set:元素不可以重复,是无序。
1、set接口的实现类
Set接口中的方法和Collection一致。
|--HashSet: 内部数据结构是哈希表 ,是不同步的。
|-- LinkedHashSet:内部数据结构是哈希表和链表,有序的
|--TreeSet: 内部数据结构是二叉树,可以对Set集合中的元素进行排序。是不同步的。
2、HashSet: 内部数据结构是哈希表 ,是不同步的
2.1HashSet哈希表说明
哈希表中,存储时是根据元素的特点,来决定元素在数组中的位置,这个过程是通过哈希算法来实现的。
用哈希表存储的好处:查询非常快!不需要遍历集合中的每个元素,只需要拿到该元素的信息,然后利用哈希算法,就可以找到该元素所在的位置
2.2HashSet如何保证该集合的元素唯一性呢?
哈希表确定元素是否相同
是通过对象的hashCode和equals方法来完成对象唯一性的。
if(this.hashCode()== obj.hashCode()&& this.equals(obj))
【1】如果对象的hashCode值不同(对象的hashCode方法),那么不用判断equals方法,就直接存储到哈希表中。
【2】如果对象的hashCode值相同,那么要再次判断对象的equals方法是否为true。
【3】如果equals方法为true,视为相同元素,不存。如果为false,那么视为不同元素,就进行存储。
注意:如果哈希值不同,是不需要判断equals。
其中,hashCode方法确定位置是否相同;equals方法确定对象内容是否相同。
2.3HashSet集合使用注意
(1)java类库中已有的对象
使用java类库中已有的对象时,看看它是否有覆盖Object对象的hashCode方法和equals方法,如果没有,则需要自己去覆盖;如果有,则不用。
举例说明:
String对象覆盖了Object的hashCode方法和equals方法,所以使用时,直接拿来用即可,不用考虑对象的覆盖情况。
(2)使用自定义对象
使用自定义对象,需要自己手动覆盖hashCode方法和equals方法。
举例说明:
我自己定义了一个people类,里面有name和age属性,我希望这个实例化后的people对象存入HashSet中,能通过name和age两个属性来判断,这个对象是否已经在HashSet集合中存储过了,为了确保存取的唯一性,我需要覆盖hashCode方法和equals方法。
(3)问题与解答
问题:项目开发中,使用自定义对象,使用hashSet,如何保证唯一性?
答案:在自定义对象中,添加两个方法,一个hashCode()方法,一个equals方法,里面书写自己的判断逻辑,达到确保存放对象唯一性的目的!
记住:
一般情况下,如果定义的类会产生很多对象,比如人,学生,书,通常都需要覆盖equals,hashCode方法。建立对象判断是否相同的依据。
(4)hashCode方法,equals方法注意
hashCode方法:里面书写生成位置的逻辑,可以给现有条件下,自己生成的的数值,再去乘以一些加到奇数,增加随机性。
equals方法:
【1】进行比较内容
【2】添加健壮性判断:<1>可以看看两个比较的对象的地址是否相同,如果相同,则不进行存入;<2>看看比较的两个对象的类型是否相同,如果类型不同,则直接抛出异常等!!!
(5)自定义对象people使用HashSet集合
【1】People对象(覆盖hashCode方法和equals方法)
package list_set;
public class People {
private String name;
private int age;
public People(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 int hashCode() {
//name的哈希值+age的值乘以某个奇数
return this.name.hashCode()+age*37;
}
@Override
public boolean equals(Object obj) {
People p=(People)obj;
//比较姓名和年龄是否相等
if(this.getName().equals(p.getName())&&this.getAge()==p.getAge()){
return true;
}
return false;
}
}
【2】HashSet集合使用
package list_set;
import java.util.HashSet;
import java.util.Iterator;
public class PeopleSet {
public static void main(String[] args) {
HashSet set=new HashSet();
set.add(new People("张三",23));
set.add(new People("张三",23));//去除重复,保证唯一性 自定义hashcode方法和equals方法
set.add(new People("李四",24));
set.add(new People("王五",24));
set.add(new People("赵六",25));
set.add(new People("你好",26));
//遍历
for(Iterator it=set.iterator();it.hasNext();){
People p=(People)it.next();
System.out.println(p.getName()+":"+p.getAge());
}
}
}
结果:(保证了存入对象的唯一性)
赵六:25
你好:26
张三:23
李四:24
王五:24
2.4 实现功能:去除ArrayList中的重复元素
(1)字符串对象实现
package list_set;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
/*
* 去除ArrayList中的重复元素
* 元素是:String对象(jdk中已有对象)
*/
public class Remove_repeat1 {
public static void main(String[] args) {
ArrayList ar = new ArrayList();
ar.add("张三");
ar.add("张三");
ar.add("李四");// 去除重复
ar.add("王五");
ar.add("李白");
ar = noReapeat(ar);// 去重操作
// 遍历
for (Iterator it = ar.iterator(); it.hasNext();) {
System.out.println(it.next());
}
}
/*
* 去重操作
* 该容器为ar
* 1、创建临时容器temp
* 2、遍历容器ar,一个个进行判断,看看该元素是否在temp中;
* 如果在temp中,则不存;否则,进行存储
*/
public static ArrayList noReapeat(ArrayListar) {
//临时容器temp
ArrayList temp=new ArrayList();
//遍历容器ar
for (Iterator it = ar.iterator(); it.hasNext();) {
Object obj=it.next();//当前元素
//temp中不存在当前元素,进行存储
/*
* 成功了!
* 查看源码:发现ArrayList中的contains方法最终的判断依据是:对象的equals方法
* 就是说,如果对象有自己的equals方法,就调用自己的进行判断;
* 如果没有,则调用Object对象的equals方法进行判断。
* 此处:String对象,自己有equals方法,覆盖了Object的方法,这里equals方法比较的是两个字符串对象
* 的内容,而不是两个对象的地址,因此达到了比较的目的。
* 因此,达到了去重的目的。
*/
if(!temp.contains(obj)){
temp.add(obj);
}
}
return temp;//返回临时容器
}
}
(2)自定义对象实现
【1】自定义对象People
package list_set;
public class People {
private String name;
private int age;
public People(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 boolean equals(Object obj) {
People p=(People)obj;
//比较姓名和年龄是否相等
if(this.getName().equals(p.getName())&&this.getAge()==p.getAge())
{
return true;
}
return false;
}
}
【2】去重代码测试类
package list_set;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
/*
* 去除ArrayList中的重复元素
* 元素是:自定义对象
*/
public class Remove_repeat {
public static void main(String[] args) {
ArrayList ar = new ArrayList();
// AbstractList abs=new ArrayList();
// abs.add(1);
ar.add(new People("张三", 23));
ar.add(new People("张三", 23));// 重复,如何去除???
ar.add(new People("李四", 24));
ar.add(new People("王五", 24));
ar.add(new People("赵六", 25));
ar.add(new People("你好", 26));
ar = noReapeat(ar);// 去重操作
for (Iterator it = ar.iterator(); it.hasNext();) {
People p = (People) it.next();
System.out.println(p.getName() + ":" + p.getAge());
}
}
/*
* 去重操作思路:建立一个临时容器temp,把容器ar去重后的数据放到临时容器中,最终将集合指向这个临时容器即可
* 做法:
* 1、将容器ar中的元素,一个个放入到临时容器temp中,并首先在temp中是否存在该元素,使用contains方法判断
* 2、如果存在,则不进行存储;如果不存在,则存储到temp中
*/
public static ArrayList noReapeat(ArrayListar) {
//临时容器temp
ArrayList temp=new ArrayList();
for (Iterator it = ar.iterator(); it.hasNext();) {
People p = (People) it.next();
//发现,这样判断后,仍旧没有什么效果,还是达不到去重的效果!为什么?
/*思考:难道contains方法失效???
* 查询ArrayList的源码,查看contains方法的实现,
* 发现contains方法,最终调用的是:Object对象的equals方法进行比较的,
* 原因:此处的Object对象是:People,因为People没有实现equals方法,
* 所以最终实际上调用的就是Object对象的equals方法
* 即:可以知道,这个Object对象的方法,比较的是两个对象的地址是否相同。
* 因此,没有达到去重的目的,因为任何的对象,创建的地址都是不同的!
*
* 推论:我们想要达到去重的效果,就得在自己的自定义对象中People,
* 覆盖Object对象的equals方法,加入自己的自定义内容(自己的判断逻辑),达到去重的目的。
*
* 结果:证实这一推论是成立的。达到了去重的效果
*
* 比较HashSet
* ArrayList vs HashSet
* HashSet 需要覆盖 hashCode方法和equals方法达到去重的目的
* ArrayList 需要覆盖equals方法达到去重的目的。
*
* 发现:
* ArrayList中的equals方法:如果对象有自己的equals方法,则调用自己所写;
* 如果没有,调用的是Object的equals方法
*/
if(!temp.contains(p)){
temp.add(p);//不存在,进行添加
}
}
return temp;
}
}
(3)字符串对象实现和自定义对象实现比较分析
【1】字符串对象实现分析:
在判断元素是否存在于集合中的时候,只需用集合的contains方法判断即可。
分析:
查看源码:发现ArrayList中的contains方法最终的判断依据是:对象的equals方法。
就是说,如果对象有自己的equals方法,就调用自己的进行判断;
如果没有,则调用Object对象的equals方法进行判断。
此处:String对象,自己有equals方法,覆盖了Object的方法,这里equals方法比较的是两个字符串对象的内容,而不是两个对象的地址,因此达到了比较的目的。
【2】自定义对象实现分析:
在判断元素是否存在于集合中的时候,不仅需用集合的contains方法判断;而且我们还要给对象写个equals方法。
假设自定义对象是:People
分析:
分析ArrayList源码,发现contains方法,最终调用的是:Object对象的equals方法进行比较的!
原因:此处的Object对象是:People,因为People没有实现equals方法,所以最终实际上调用的就是Object对象的equals方法
因此,可以知道,这个Object对象的方法,比较的是两个对象的地址是否相同。
所以,此时,利用contains方法(底层实现:equals())去判断两个对象是否相同,是没有意义的,因为任何的对象,创建的地址都是不同的!
推论:我们想要判断,就得在自己的自定义对象中People,
覆盖Object对象的equals方法,加入自己的自定义内容(自己的判断逻辑)。
(3)HashSet 和 ArrayList比较
结论:
HashSet和ArrayList的数据结构不同,所以,对于一些情况,判断的依据会有所不同!
主要体现在:
(1)查找,判断元素是否相同 使用的方法不同。
(2)删除一个元素使用该方法不同
ArrayList集合只是用equals方法,HashSet同时使用hashCode和equals方法
【1】判断元素是否相同(contains方法)原理:
ArrayList集合:判断元素是否相同,仅仅使用元素的equals方法
HashSet集合:判断元素是否相同,同时使用hashCode方法和equals方法【先:hashCode方法 后:equals方法】
结论:
<1>我们使用ArrayList时,想要保证ArrayList集合中的元素是唯一的,需要在ArrayList中存储的对象中覆盖对象的equals方法(自定义对象)
<2>我们使用HashSet集合时,想要保证HashSet集合中的元素是唯一的,需要在HashSet中存储的对象中覆盖对象的equals方法和hashCode方法。(自定义对象)
备注:如果集合中存储的对象不是自定义对象,而是jdk中已有的对象,而且该对象中已经覆盖了equals方法或者equals方法和hashCode方法,我们在用ArrayList集合和HashSet集合时就无需考虑给对象覆盖方法的事,直接拿来用即可。
【2】删除一个元素原理(remove方法)
ArrayList集合:然后原理利用equals方法查找,把集合中的该东西删除掉。
HashSet集合:判断元素是否相同,同时使用hashCode方法和equals方法查找,然后删除掉。
3、LinkedHashSet(哈希表和链表实现)--有序的
特殊:有序的
具有可预知迭代顺序的 Set 接口的哈希表和链接列表实现。此实现与 HashSet 的不同之外在于,后者维护着一个运行于所有条目的双重链接列表。
此链接列表定义了迭代顺序,即按照将元素插入到 set 中的顺序(插入顺序)进行迭代。【保证了元素的有序性,元素插入顺序和迭代顺序相同】