简介:
Set集合最大的特点就是集合内的元素不允许重复,Set也是Collection的子接口;
在JDK1.9之前Set集合与Collection集合的定义并无差别,Set继续使用了Collection接口中提供的方法进行操作,但是从JDK1.9之后,Set集合也像List集合一样扩充了一些static方法。
定义:
public interface Set<E> extends Collection<E>
需要注意的是Set集合并不完全像List集合那样扩充了许多的新方法,无法使用List集合中提供的get()方法,也就是说无法实现指定索引数据的获取。
从JDK1.9之后Set集合也提供了像List集合之中类似的of()的静态方法;
代码实现:
import java.util.Set
public class MAIN {
public static void main(String[] args) {
// 进行Set集合的数据保存
Set<String> set = Set.of("Hello","World","Hello","World");
set.forEach(System.out::println);
}
}
输出结果: |
Exception in thread "main" java.lang.IllegalArgumentException: duplicate element: Hello
|
当使用of()方法的时候如果发现集合之中存在有重复的元素则会直接抛出异常这与传统的Set集合不保存重复元素的特点相一致,只是提前抛出异常。
Set集合的用法一定是使用其子类进行实例化的,所以Set接口之中有两个常用的子类:HashSet、TreeSet
Set接口子类 - HashSet类:
HashSet类是Set接口中使用最多的子类,其最大的特点就是保存的数据是无序的,其继承关系如下:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
这种继承的形式和ArrayList非常相似。
代码实现:
import java.util.HashSet;
import java.util.Set;
public class MAIN {
public static void main(String[] args) {
// 进行Set集合的数据保存
Set<String> set = new HashSet<String>();
set.add("Hello");
set.add("Hello"); // 重复元素
set.add("World");
set.add("World"); // 重复元素
set.add("China");
set.forEach(System.out::println);
}
}
输出结果
可以发现HashSet的特点:
- 不允许保存重复的元素
- 保存的数据是无序的
Set接口子类 - TreeSet类
TreeSet与HashSet最大的区别在于TreeSet集合里面保存的数据是有序的(按照首字母顺序排序)。
public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, java.io.Serializable
在这个子类中依然继承了AbstractSet抽象父类,同时有实现了一个NavigableSet的父接口。
NavigableSet<E> extends SortedSet<E> extends Set<E> |
|
public interface NavigableSet<E> extends SortedSet<E>
|
public interface SortedSet<E> extends Set<E>
|
代码实现:
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class MAIN {
public static void main(String[] args) {
Set<String> set1 = new TreeSet<String>();
set1.add("Hello");
set1.add("Hello"); // 重复元素
set1.add("World");
set1.add("World"); // 重复元素
set1.add("China");
set1.forEach(System.out::println);
}
}
输出结果:
当使用TreeSet保存数据的时候,保存的数据都会自动按照数据首字母升序排序处理。
TreeSet排序说明(利用Comparable接口识别重复数据):
前面的程序代码运行结果显示TreeSet中保存的数据是允许进行排序的,但是这个类必须要实现Comparable接口,因为只有实现了此接口才能够确认出对象的大小关系
如果没有实现接口:
import java.util.Set;
import java.util.TreeSet;
public class MAIN {
public static void main(String[] args) {
Set<Person> set2 = new TreeSet<Person>();
set2.add(new Person("张三",14));
set2.add(new Person("张三",14)); // 重复数据
set2.add(new Person("李四",11));
set2.add(new Person("王五",15));
set2.add(new Person("王五",13)); // 姓名相同,年龄不同
set2.add(new Person("狗蛋",13)); // 年龄相同,姓名不同
set2.add(new Person("麻子",17));
set2.forEach(System.out::print);
}
}
class Person{
private String name;
private int age;
public Person(String name,int age) {
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;
}
public String toString() {
return "姓名:" + this.name + "\t年龄:" + this.age;
}
}
输出结果:
Exception in thread "main" java.lang.ClassCastException:
class Demo.Person cannot be cast to class java.lang.Comparable
(Demo.Person is in unnamed module of loader 'app';
java.lang.Comparable is in module java.base of loader 'bootstrap')
发生了异常。
方法名 | 方法体 |
add |
public boolean add(E e) {
return m.put(e, PRESENT)==null;
}
|
put |
private V put(K key, V value, boolean replaceOld) {
Entry<K,V> t = root;
if (t == null) {
addEntryToEmptyMap(key, value);
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
} else {
Objects.requireNonNull(key);
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else {
V oldValue = t.value;
if (replaceOld || oldValue == null) {
t.value = value;
}
return oldValue;
}
} while (t != null);
}
addEntry(key, value, parent, cmp < 0);
return null;
}
|
addEntryToEmptyMap
|
private void addEntryToEmptyMap(K key, V value) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
}
|
TreeSet本质上是利用TreeMap子类实现的集合数据的存储,而TreeMap(树)就需要根据Comparable来确定大小关系。
实现Comparable接口:
import java.util.Set;
import java.util.TreeSet;
public class MAIN {
public static void main(String[] args) {
Set<Person> set2 = new TreeSet<Person>();
set2.add(new Person("张三",14));
set2.add(new Person("张三",14)); // 重复数据
set2.add(new Person("李四",11));
set2.add(new Person("王五",15));
set2.add(new Person("王五",13)); // 姓名相同,年龄不同
set2.add(new Person("狗蛋",13)); // 年龄相同,姓名不同
set2.add(new Person("麻子",17));
set2.forEach(System.out::print);
}
}
class Person implements Comparable<Person>{
private String name;
private int age;
public Person(String name,int age) {
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;
}
public String toString() {
return "姓名:" + this.name + "\t年龄:" + this.age + "";
}
@Override
public int compareTo(Person per) {
if(this.age < per.age) {
return -1;
}else if(this.age > per.age) {
return 1;
}else {
return this.name.compareTo(per.name);
}
}
}
输出结果(直接利用age比较):
输出结果(对age和name都进行比较):
在使用自定义类进行对象比较处理的时候,一定要将该类之中的所有属性都一次进行大小关系的匹配,否则如果某一个或某几个属性相同的时候该集合也会认为是重复数据,所以TreeSet是利用Comparable接口来确认重复数据的。
由于TreeSet在操作过程之中需要将所有的属性都进行比较,如果属性比较多的话这样的实现操作就非常麻烦,所以在实际情况中应该首选HashSet子类进行存储。
重复元素说明:
已知TreeSet类判断元素重复的方式是通过实现Comparable接口,但是HashSet类判断元素重复的方式并不是通过实现Comparable接口完成的,而是利用的Object类中提供的方法完成的:
- 对象编码:public int hashCode();
- 对象比较:public boolean equals(Object obj);
在进行重复元素的判断的时候首先利用hashCode()进行编码的匹配,如果该编码不存在则表示数据不存在证明没有重复元素,如果该编码存在了,则进一步进行对象比较处理,如果发现重复了,则此数据是不允许保存。
如果使用的是eclipse开发工具,则可以帮助用户进行快捷生成equals()和hashCode()方法(alt+shift+s):
在生成了hashCode()和equals()方法后再使用HashSet类就可以实现保存无重复数据的操作了:
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
public class MAIN {
public static void main(String[] args) {
Set<Person> set2 = new HashSet<Person>();
set2.add(new Person("张三",14));
set2.add(new Person("张三",14)); // 重复数据
set2.add(new Person("李四",11));
set2.add(new Person("王五",15));
set2.add(new Person("王五",13)); // 姓名相同,年龄不同
set2.add(new Person("狗蛋",13)); // 年龄相同,姓名不同
set2.add(new Person("麻子",17));
set2.forEach(System.out::print);
}
}
class Person {
private String name;
private int age;
public Person(String name,int age) {
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;
}
public String toString() {
return "姓名:" + this.name + "\t年龄:" + this.age + "\n";
}
@Override
public int hashCode() {
return Objects.hash(age, name);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
return age == other.age && Objects.equals(name, other.name);
}
}
输出结果;
虽然上面有两个王五,但是是不同年龄的王五,就像身份证上有名称相同的两个人,但是他们的身份证号码是不同的,所以不能说他们是一个人,也就不能说这两个王五是重复的。
在Java程序之中真正的重复元素的判断处理是利用hashCode()与equals()方法共同完成的,而只有在排序要求(TreeSet)的时候才会使用到Comparable接口来实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)