Set接口简介
Set接口和List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set接口主要有两个实现类,分别是HashSet和TreeSet。其中,HashSet是根据对象的哈希值来确定元素在集合中的存储位置,具有良好的存取和查找性能。TreeSet则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序。
HashSet集合
HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的。接下来通过一个案例演示HashSet集合的用法。
Java import java.util.HashSet; import java.util.Iterator;
public class Example07 { public static void main(String[] args) { HashSet set = new HashSet(); // 创建HashSet集合 set.add("张三"); // 向该Set集合中添加字符串 set.add("李四"); set.add("王五"); set.add("李四"); // 向该Set集合中添加重复元素 Iterator it = set.iterator(); // 获取Iterator对象 while (it.hasNext()) { // 通过while循环,判断集合中是否有元素 Object obj = it.next(); // 如果有元素,就通过迭代器的next()方法获取元素 System.out.println(obj); } } }
|
从打印结果可以看出,取出元素的顺序与添加元素的顺序并不一致,并且重复存入的字符串对象“李四”被去除了,只添加了一次。
HashSet集合之所以能确保不出现重复的元素,是因为它在存入元素时做了很多工作。当调用HashSet集合的add()方法存入元素时,首先调用当前存入对象的hashCode()方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置。如果该位置上没有元素,则直接将元素存入,如果该位置上有元素存在,则会调用equals()方法让当前存入的元素依次和该位置上的元素进行比较,如果返回的结果为false就将该元素存入集合,返回的结果为true则说明有重复元素,就将该元素舍弃。HashSet存储元素的流程如下图所示。
根据前面的分析不难看出,当向集合中存入元素时,为了保证HashSet正常工作,要求在存入对象时,重写Object类中的hashCode()和equals()方法。在上述案例中将字符串存入HashSet时,String类已经重写了Object类中的hashCode()和equals()方法。但是如果将自定义的Student对象存入HashSet,结果又如何呢?
接下来通过一个案例演示向HashSet存储字符串。
Java import java.util.HashSet;
class Student { String id; String name;
// 创建构造方法 public Student(String id, String name) { this.id = id; this.name = name; }
// 重写toString()方法 @Override public String toString() { return id + ":" + name; } } public class Example08 { public static void main(String[] args) { HashSet hs = new HashSet(); // 创建HashSet集合 Student stu1 = new Student("1", "张三"); // 创建Student对象 Student stu2 = new Student("2", "李四"); Student stu3 = new Student("2", "李四"); hs.add(stu1); hs.add(stu2); hs.add(stu3); System.out.println(hs); } }
|
在上述代码的运行结果中,出现了两个相同的学生信息“2:李四”,这样的学生信息应该被视为重复元素,不允许同时出现在HashSet集合中。之所以没有去掉这样的重复元素,是因为在定义Student类时没有重写hashCode()和equals()方法。
接下来对Student类进行改写,假设id相同的学生就是同一个学生,改写后代码见下。
Java import java.util.HashSet; import java.util.Objects;
class Student { String id; String name;
// 创建构造方法 public Student(String id, String name) { this.id = id; this.name = name; }
// 重写toString()方法 @Override public String toString() { return id + ":" + name; }
// 重写hashCode方法 @Override public int hashCode() { return id.hashCode(); // 返回id属性的哈希值 }
// 重写equals方法 @Override public boolean equals(Object o) { // 判断是否是同一个对象,如果是,直接返回true if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; // 判断对象是否为Student类型 if (!(o instanceof Student)) return false; // 将对象强转为Student类型 Student student = (Student) o; // return (id == student.id) || (id != null && id.equals(student.id)); return Objects.equals(id, student.id); } }
public class Example09 { public static void main(String[] args) { HashSet hs = new HashSet(); // 创建HashSet集合 Student stu1 = new Student("1", "张三"); // 创建Student对象 Student stu2 = new Student("2", "李四"); Student stu3 = new Student("2", "李四"); hs.add(stu1); hs.add(stu2); hs.add(stu3); System.out.println(hs); } }
|
在上述代码中,Student类重写了Object类的hashCode()和equals()方法。在hashCode()方法中返回id属性的哈希值,在equals()方法中比较对象的id属性是否相等,并返回结果。当调用HashSet集合的add()方法添加stu3对象时,发现它的哈希值与stu2对象相同,而且id.equals(student.id)返回true,HashSet集合认为两个对象相同,因此重复的Student对象被成功去除了。
HashSet集合存储的元素是无序的,如果想让元素的存取顺序一致,可以使用Java中提供的LinkedHashSet类,LinkedHashSet类是HashSet的子类,与LinkedList一样,它也使用双向链表来维护内部元素的关系。
接下来通过一个案例学习LinkedHashSet类的用法。
Java import java.util.Iterator; import java.util.LinkedHashSet;
public class Example10 { public static void main(String[] args) { LinkedHashSet set = new LinkedHashSet(); set.add("张三"); // 向该Set集合中添加字符串 set.add("李四"); set.add("王五"); Iterator it = set.iterator(); // 获取Iterator对象 while (it.hasNext()) { // 通过while循环,判断集合中是否有元素 Object obj = it.next(); System.out.println(obj); } } }
|
通过运行结果可以看出,元素迭代出来的顺序和存入的顺序是一致的。