问题
首先让我们先带着几个问题来进行接下来的学习
1.Set接口和List接口的区别是什么?
2.Set接口有什么特点?
3.Set接口的实现类有哪些?
接下来正式开始分析讲解Set接口
1.Set接口和List接口的区别是什么?
- List允许存储重复的元素, 而Set接口是不允许存在重复的元素的(元素可以为Null)
- List存储的数据是有序的(存入和取出一致), Set接口存储的数据是无序的(存入和取出不一致)
Set接口继承了Collection接口, 其继承了Collection的所有方法, 而Set接口比较常用的实现类有HashSet, LinkedHashSet, TreeSet,我们首先来看一段代码, 来感受Set接口中的元素不可重复
以TreeSet举例

通过代码,我们可以很直观的看到,我已经在TreeSet中添加了三条数据,其中两条数据相同,在打印输出的时候只输出了1和2,这就是Set接口的元素不可重复性,那么它的内部是怎么实现的呢?这个问题后边会有专门的介绍哦
那么接下来我们来看一下Set无序是什么意思
这里我们使用的是HashSet

可以看到插入的值和取出的值是不对应的,但是要注意,HashSet集合在自己的内部是有自己的排序的,而且随着我们插入顺序的变化,它内部的排序也会发生变化
2.Set接口有什么特点?
其实这个问题在前面早已进行了解答,细心地读者想必应该已经了解了吧,没错,就是无序性和不可重复性
3.Set接口的实现类基础理解
Set的实现类(常用)
- HashSet
- TreeSet
- LinkedHashSet
那么首先是第一个 HashSet
此类实现Set接口,由哈希表(实际为HashMap实例)支持。 对集合的迭代次序不作任何保证; 特别是,它不能保证数据在一段时间内保持不变。 这个类允许null元素。注意:HashSet是不同步的
HashSet的方法
- boolean add(E e)
将指定的元素添加到此集合(如果尚未存在)。 - void clear()
从此集合中删除所有元素。 - Object clone()
返回此 HashSet实例的浅层副本:元素本身不被克隆。 - boolean contains(Object o)
如果此集合包含指定的元素,则返回 true 。 - boolean isEmpty()
如果此集合不包含元素,则返回 true 。 - Iterator iterator()
返回此集合中元素的迭代器。 - boolean remove(Object o)
如果存在,则从该集合中删除指定的元素。 - int size()
返回此集合中的元素数(其基数)。 - Spliterator spliterator()
在此集合中的元素上创建late-binding和故障快速 Spliterator 。
因为元素顺序不固定的原因,我们无法使用HashSet来直接获取想要的数据,传入元素时,调用HashCode方法获取hash值,然后决定存储位置,我们可以来总结一下,HashSet对象中不能存储相同的数据,存储数据时是无序的。但是HashSet存储元素的顺序并不是按照存入时的顺序(和List显然不同) 是按照哈希值来存的所以取数据也是按照哈希值取的
代码演示
public class SetTest {
public static void main(String[] args) {
HashSet<String> set = new HashSet<String>();
// 添加元素
set.add("c");
set.add("b");
set.add("a");
// Iterator<String> it = set.iterator(); 迭代器
System.out.println(set.size());
// 从集合中删除元素 传入的参数就是想要删除的数据
boolean b = set.remove("a");
System.out.println(set.size());
// 遍历集合
for(String s:set){
System.out.println(s);
}
}
}
执行结果

关于HashSet一些补充
- HashSet是没有修改方法的,因为它是无序的,所以我们不能通过索引来查找某个元素再对其进行修改
- 在遍历的时候可以使用增强for循环或者是迭代器
那么,我们现在就来探究一下,HashSet集合到底是怎么实现了不可重复的呢?
它是发现重复元素就不会再进行添加呢?还是会覆盖前面的重复元素呢?
代码测试
import java.util.HashSet;
public class HashSetTest {
public static void main(String[] args) {
HashSet<Person> set = new HashSet<Person>();
set.add(new Person("张宇"));
set.add(new Person("张宇"));
set.add(new Person("张宇"));
set.add(new Person("张宇"));
System.out.println("对象地址:"+set+" 元素个数"+set.size());
}
}
// 创建一个新的类 尝试实现HashSet内部的去除重复效果
class Person{
public String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}

通过运行结果我们可以看出,就算是名字全部一样的对象,照样不被HashSet算作为重复的对象,那么如果我们想要让它们算作同一个对象,实现"去除重复"的效果,我们应该怎么做呢?不妨试一试在Person中重写equals()方法
class Person{
public String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public boolean equals(Object obj) {
// 如果对象的地址一样那么肯定就是同一个对象啊 这毋庸置疑吧 所以直接返回true就好
if(this == obj) {
return true;
}
// obj如果是Person类亦或是直接间接地继承了Person类则返回true
if(obj instanceof Person) {
Person p = (Person) obj;// 还原类型为Person
if(this.name.equals(p.name)) { // 判断对象中属性是否相等
return true;
}
}
return false;
}
}
重写之后我们再进行测试

我们发现,结果还是无法满足我们的要求,是我们重写的equals有问题吗?

新建了两个对象调用重写后的equals()方法进行测试,返回的结果是true,是没问题的,那么就是说在HashSet内部不止这一种无重复原则,还有其他的方法来判断是否重复,那么再重写equals()通常会伴随着另一个方法,就是hashCode方法,那我们就再重写hashCode方法来验证以下猜想是否正确
再次重写方法 HashCode()
class Person{
public String name;
public Person(String name) {
this.name = name;
}
public boolean equals(Object obj) {
// 如果对象的地址一样那么肯定就是同一个对象啊 这毋庸置疑吧 所以直接返回true就好
if(this == obj) {
return true;
}
// obj如果是Person类亦或是直接间接地继承了Person类则返回true
if(obj instanceof Person) {
Person p = (Person) obj;// 还原类型为Person
if(this.name.equals(p.name)) { // 判断对象中属性是否相等
return true;
}
}
return false;
}
public int hashCode() {
//如果两个对象的name返回的HashCode码一致,我们就认为它们两个是相同的
return this.name.hashCode();
}
public String getName() {
return this.name;
}
}
执行测试

现在结果是不是就和我们想要的结果一致了
本文探讨了Set接口与List接口的区别,Set接口的特点在于元素的无序性和不可重复性。主要讲解了HashSet、TreeSet和LinkedHashSet三个实现类,通过示例代码展示了它们的行为特性,特别是HashSet的元素存储和取出不一致,以及如何通过重写equals()和hashCode()方法确保对象的唯一性。
1240





