(四)Set集合
1.Set集合概述和特点
Set集合特点
- 不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通for循环
Set集合练习
- 存储字符串并遍历
import java.util.HashSet;
import java.util.Set;
public class SetDemo {
public static void main(String[] args) {
//创建集合对象
Set<String> set = new HashSet<>();//HashSet对集合的迭代顺序不作任何保证
//添加元素
set.add("hello");
set.add("java");
set.add("world");
//不包含重复元素
set.add("world");//结果只有一个world
//遍历集合
for (String s : set) {
System.out.println(s);
}
}
}
2.哈希值
哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
Object类中有一个方法可以获取对象的哈希值
- public int hashCode():返回对象的哈希码值
对象的哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
3.HashSet集合概述和特点
HashSet集合特点
- 底层数据结构是哈希表
- 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以是不包含重复元素的集合
HashSet集合练习
- 存储字符串并遍历
import java.util.HashSet;
import java.util.Iterator;
public class HashSetDemo {
public static void main(String[] args) {
HashSet<String> sh = new HashSet<>();
sh.add("hello");
sh.add("world");
sh.add("java");
//迭代器遍历
Iterator<String> it = sh.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
//增强for遍历
for(String s : sh){
System.out.println(s);
}
}
}
4.HashSet集合保证元素唯一性源码分析
HashSet集合添加一个元素过程:
HashSet集合存储元素:
- 要保证元素唯一性,需要重写hashCode()和equals()
5.案例(HashSet集合存储学生对象并遍历)
要求:学生对象的成员变量值相同,我们就认为是同一个对象
代码实现:
创建学生类(需要重写hashCode()和equals()方法来保证元素的唯一性)
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student() {
}
public Student(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;
}
//重写重写hashCode()和equals()方法来保证元素的唯一性
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
创建测试类
import java.util.HashSet;
import java.util.Iterator;
public class HashSetDemo {
public static void main(String[] args) {
HashSet<Student> sh = new HashSet<>();
Student s1 = new Student("学生1", 20);
Student s2 = new Student("学生2", 21);
Student s3 = new Student("学生3", 22);
Student s4 = new Student("学生1",20);//与s1相同
sh.add(s1);
sh.add(s2);
sh.add(s3);
sh.add(s4);//结果并未显示s4,因为与s1位同一个元素,在学生类定义中重写了hashCode()和equals()方法确保了元素的唯一性
//迭代器遍历集合
Iterator<Student> it = sh.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s.getName() + ", " + s.getAge());
}
//增强for遍历集合
for(Student s : sh) {
System.out.println(s.getName() + ", " + s.getAge());
}
}
}
6.LinkedHashSet集合概述和特点
LinkedHashSet集合特点
- 哈希表和链表实现的Set接口,具有可预测的迭代次序
- 由链表保证元素有序,也就是 说元素的存储和取出顺序是一致的
- 由哈希表保证元素唯一,也就是说没有重复的元素
LinkedHashSet集合练习
- 存储字符串并遍历
import java.util.LinkedHashSet;
public class LinkedHashSetDemo {
public static void main(String[] args) {
//LinkedHashSet集合保证了元素的唯一性也保证了存储和取出的顺序的一致性
LinkedHashSet<String> lh = new LinkedHashSet<>();
lh.add("hello");
lh.add("java");
lh.add("world");
lh.add("hello");//元素唯一性,所以结果不显示多余的hello字符串
for (String s : lh) {
System.out.println(s);
}
}
}
7.TreeSet集合概述和特点
TreeSet集合特点
- 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方式取决于构造方法
TreeSet():根据其元素的自然排序进行排序
TreeSet(Comparetor comparator):根据指定的比较器进行排序
- 没有带索引的方法,所以不能使用普通for循环遍历
- 由于是Set集合,所以不包含重复元素
TreeSet集合练习
- 存储整数并遍历
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Integer> ts = new TreeSet<>();
//添加元素
ts.add(2);
ts.add(3);
ts.add(1);
//因为它是Set集合,所以不包含重复元素
ts.add(2);
for(Integer it: ts) {
System.out.print(it);//结果为 1 2 3
}
}
}
8.自然排序Comparable的使用
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现Comparable接口,重写compareTo(T o)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
练习:
- 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
- 要求:按照年龄从小到大牌,年龄相同时,按照姓名的字母顺序排序
代码实现:
定义学生类:
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
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 Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student s) {
// return -1;//降序
// return 0;//元素重复
// return 1;//升序
int flag = this.age - s.age; //this在前为升序,在后为降序
int flag2 = flag == 0 ? this.name.compareTo(s.name) : flag; //String类自带compareTo方法
return flag2;
}
}
创建测试类:
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("学生1",22);
Student s2 = new Student("学生2",23);
Student s3 = new Student("学生3",33);
Student s4 = new Student("学生4",33);
Student s5 = new Student("学生4",33);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);//s5与s4元素重复所以结果不显示
for(Student s:ts) {
System.out.println(s.getName()+", "+s.getAge());
}
}
}
9.比较器排序Comparator的使用
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要和次要条件来写
练习:
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
代码实现:
定义一个标准的学生类:(此处省略)
定义测试类:
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int flag = s1.getAge() - s2.getAge();
int flag2 = flag == 0 ? s1.getName().compareTo(s2.getName()) : flag;
return flag2;
}
});
Student s1 = new Student("学生1",22);
Student s2 = new Student("学生2",23);
Student s3 = new Student("学生3",33);
Student s4 = new Student("学生4",33);
Student s5 = new Student("学生4",33);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);//s5与s4元素重复所以结果不显示
for(Student s:ts) {
System.out.println(s.getName()+", "+s.getAge());
}
}
}
分析:
这里o1表示位于前面的对象,o2表示后面的对象
- 返回-1(或负数),表示不需要交换01和02的位置,o1排在o2前面,asc
- 返回1(或正数),表示需要交换01和02的位置,o1排在o2后面,desc
下面来用我们之前的结论解释为什么 return o2.a - o1.a 就是降序了:
首先o2是第二个元素,o1是第一个元素。无非就以下这些情况:
①: o2.a > o1.a: 那么此时返回正数,表示需要调整o1,o2的顺序,也就是需要把o2放到o1前面,这不就是降序了么。
②:o2.a < o1.a : 那么此时返回负数,表示不需要调整,也就是此时o1 比 o2大, 不还是降序么。
10.案例(不重复的随机数)
编写一个程序,获取10个1~20之间的随机数,要求随机数不能重复,并在控制台输出
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
public class SetDemo {
public static void main(String[] args) {
//创建集合对象
//Set<Integer> set = new HashSet<>();//HashSet集合是无序且元素唯一
Set<Integer> set = new TreeSet<>();//TreeSet集合是有序且元素唯一
//创建随机数对象
Random r = new Random();
//判断集合的长度是否小于10
while (set.size() < 10) {
int sum = r.nextInt(20) + 1;
set.add(sum);
}
for(Integer i :set) {
System.out.println(i);
}
}
}