一. Set概述
1. Set的特点
元素是无序的;元素不可以重复。因为该集合体系无索引。
(无序:存入和取出的顺序不一定一致)
2. Set的功能
Set集合的功能和Collection是一致的
3. Set的子类
常见子类:HashSet和TreeSet
二. HashSet如何保证元素的唯一性
往HashSet中添加元素add(Object o)时:
HashSet首先会调用元素的hashCode方法得到元素的哈希值,然后通过移位等运算,得出该元素在哈希表中的存储位置
- 如果该存储位置没有任何元素存储,那么该元素可以直接存储到该位置上
- 如果该存储位置已存有其它元素,那么HashSet会调用该元素的equals方法与已有元素作比较:如果equals方法返回true,那么该元素视为重复元素,不允许添加;如果equals方法返回false,则允许添加
三. HashSet存储字符串对象
1. 验证HashSet的无序性
存入和取出的顺序不一定一致
import java.util.*;
class HashSetDemo{
public static void main(String[] args){
HashSet hs=new HashSet();
hs.add("java01");
hs.add("java03");
hs.add("java02");
hs.add("java04");
Iterator it=hs.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}
运行结果是:
2. 验证HashSet的唯一性
import java.util.*;
class HashSetDemo{
public static void main(String[] args){
HashSet hs=new HashSet();
hs.add("java01");
hs.add("java01");
hs.add("java03");
hs.add("java02");
hs.add("java03");
hs.add("java04");
Iterator it=hs.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
}
}
运行结果是:
3. 分析HashSet中不能添加相同字符串的原因
HashSet hs=new HashSet();
hs.add("java01");
hs.add("java01");
(1)添加第2个“java01”时,HashSet会调用“java01”的hashCode方法
查阅字符串的hashCode方法可知,内容相同的字符串哈希值是相同的(String类复写了Object类中的hashCode方法)
(2)当2个字符串的哈希值相同时,HashSet会调用equals方法
查阅字符串的equals方法可知,字符串的equals方法比较的是2个字符串的内容值,内容相同的字符串equals方法返回true
(3)所以2个内容值相同的字符串,在HashSet中视为重复元素,不允许重复添加
四. HashSet存储自定义对象
1. HashSet添加重复的自定义对象
需求:
往HashSet集合中存入自定义对象Person,姓名和年龄相同为重复元素
(已知Object类的equals方法,比较的是2个对象的地址值,因此需要复写Person类的equals方法,改成比较2个对象的内容值)
import java.util.*;
class Person{
private String name;
private int age;
Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
//复写Person类中的equals方法
public boolean equals(Object obj){
//传给Person类中equals方法的参数还是Object类对象,所以要判断是否传的是Person子类实例
if(!(obj instanceof Person))
return false;
//向下转型
Person p=(Person)obj;
//添加打印语句,查看add函数是怎样调用底层的equals函数的
System.out.println(this.name+"..."+p.name);
return this.name.equals(p.name)&&this.age==p.age;
}
}
class HashSetDemo{
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));
hs.add(new Person("a2",12));
Iterator it=hs.iterator();
while (it.hasNext()){
//向下转型
Person p=(Person)it.next();
System.out.println(p.getName()+"::"+p.getAge());
}
}
}
运行结果是:
2. 分析去重失败的原因
(1)4个元素的哈希值都是不同的,所以没有调用equals方法,没有进行去重
(2)Object类的hashCode方法(如果不重写的话),返回的是该对象在JVM的堆上的内存地址,不同对象的内存地址肯定是不同的,所以不同对象返回的哈希值也是不同的
(3)解决方法:复写Person类的hashCode( ),把各个自定义对象的哈希值写死;当哈希值相同时,HashSet会调用重写过的equals方法来比较2个Person类对象是否相同
3. 写死Person类对象的哈希值,HashSet添加重复的自定义对象
统一把哈希值写死为60
import java.util.*;
class Person{
private String name;
private int age;
Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
//复写hashCode方法
public int hashCode()
{
//添加打印语句,查看hashCode函数是怎样被调用的
System.out.println(this.name+"...hashCode");
return 60;
}
//复写Person类中的equals方法
public boolean equals(Object obj){
//传给Person类中equals方法的参数还是Object类对象,所以要判断是否传的是Person子类实例
if(!(obj instanceof Person))
return false;
//向下转型
Person p=(Person)obj;
//添加打印语句,查看add函数是怎样调用底层的equals函数的
System.out.println(this.name+"..."+p.name);
return this.name.equals(p.name)&&this.age==p.age;
}
}
class HashSetDemo{
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));
hs.add(new Person("a2",12));
Iterator it=hs.iterator();
while (it.hasNext()){
//向下转型
Person p=(Person)it.next();
System.out.println(p.getName()+"::"+p.getAge());
}
}
}
运行结果是:
4. 按条件设定Person类对象的哈希值,HashSet添加重复的自定义对象
- 根据Person类对象中的成员变量name和age来生成哈希值
- 成员变量name为String类对象,相同内容的String字符串会生成相同的哈希值
import java.util.*;
class Person{
private String name;
private int age;
Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
//复写hashCode方法
public int hashCode()
{
//添加打印语句,查看hashCode函数是怎样被调用的
System.out.println(this.name+"...hashCode");
//根据Person类对象中的成员变量name和age来生成哈希值
return name.hashCode()+age;
}
//复写Person类中的equals方法
public boolean equals(Object obj){
//传给Person类中equals方法的参数还是Object类对象,所以要判断是否传的是Person子类实例
if(!(obj instanceof Person))
return false;
//向下转型
Person p=(Person)obj;
//添加打印语句,查看add函数是怎样调用底层的equals函数的
System.out.println(this.name+"..."+p.name);
return this.name.equals(p.name)&&this.age==p.age;
}
}
class HashSetDemo{
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));
hs.add(new Person("a2",12));
Iterator it=hs.iterator();
while (it.hasNext()){
//向下转型
Person p=(Person)it.next();
System.out.println(p.getName()+"::"+p.getAge());
}
}
}
运行结果是:
五. HashSet判断和删除的依据
1. 判断是否存在contains(Object o)
- contains(Object o):底层调用的是equals(Object o)
- 调用equals(Object o)之前要先判断哈希值是否相同
- 因此自定义对象需要复写hashCode( )和equals(Object o)
import java.util.*;
class Person{
private String name;
private int age;
Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
//复写hashCode方法
public int hashCode()
{
//添加打印语句,查看hashCode函数是怎样被调用的
System.out.println(this.name+"...hashCode");
//根据Person类对象中的成员变量name和age来生成哈希值
return name.hashCode()+age;
}
//复写Person类中的equals方法
public boolean equals(Object obj){
//传给Person类中equals方法的参数还是Object类对象,所以要判断是否传的是Person子类实例
if(!(obj instanceof Person))
return false;
//向下转型
Person p=(Person)obj;
//添加打印语句,查看add函数是怎样调用底层的equals函数的
System.out.println(this.name+"..."+p.name);
return this.name.equals(p.name)&&this.age==p.age;
}
}
class HashSetDemo{
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));
//判断是否存在对象("a1",11)
System.out.println("a1: "+hs.contains(new Person("a1",11)));
}
}
运行结果是:
2. 删除元素
- remove(Object o):底层调用的是equals(Object o)
- 调用equals(Object o)之前要先判断哈希值是否相同
- 因此自定义对象需要复写hashCode( )和equals(Object o)
import java.util.*;
class Person{
private String name;
private int age;
Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
//复写hashCode方法
public int hashCode()
{
//添加打印语句,查看hashCode函数是怎样被调用的
System.out.println(this.name+"...hashCode");
//根据Person类对象中的成员变量name和age来生成哈希值
return name.hashCode()+age;
}
//复写Person类中的equals方法
public boolean equals(Object obj){
//传给Person类中equals方法的参数还是Object类对象,所以要判断是否传的是Person子类实例
if(!(obj instanceof Person))
return false;
//向下转型
Person p=(Person)obj;
//添加打印语句,查看add函数是怎样调用底层的equals函数的
System.out.println(this.name+"..."+p.name);
return this.name.equals(p.name)&&this.age==p.age;
}
}
class HashSetDemo{
public static void main(String[] args) {
HashSet hs=new HashSet();
hs.add(new Person("a1",11));
hs.add(new Person("a2",12));
hs.add(new Person("a3",13));
//删除对象("a3",13)
System.out.println(hs.remove(new Person("a3",13)));
Iterator it=hs.iterator();
while (it.hasNext()){
//向下转型
Person p=(Person)it.next();
System.out.println(p.getName()+"::"+p.getAge());
}
}
}
运行结果是:
六. HashSet总结
- 底层数据结构是哈希表
- 元素的存储位置是由元素的哈希值经过移位等运算确定的
- HashSet是通过元素的2个方法,hashCode()和equals(o)来保证元素的唯一性
- 存入自定义的HashSet对象时,需要复写hashCode()和equals()
- 对于判断元素是否存在、添加、删除等操作,HashSet集合依赖的方法是元素的hashCode()和equals()
- 定义合适的hashCode()可以提高判断的效率