目录
Set接口
Set接口概述
一个不包含重复元素的 collection。 ( Collection接口 )
HashSet类
HashSet 类实现了Set接口,线程不安全,效率高
HashSet类概述
a.不保证 set 的迭代顺序
b.特别是它不保证该顺序恒久不变。
HashSet如何保证元素唯一性
a.底层数据结构是哈希表(元素是链表的数组)
b.哈希表依赖于哈希值存储
c.添加功能底层依赖两个方法:
int hashCode()
boolean equals(Object obj)
Set案例
(由于Set是一个接口,需具体类实例化,这里用hashSet类举例)
案例1:存储字符串并遍历
import java.util.HashSet;
import java.util.Set;
public class SetTest1 {
public static void main(String[] args) {
//创建Set集合对象
Set<String> set =new HashSet<>();
//向set集合添加元素
set.add("java");
set.add("hive");
set.add("spark");
set.add("flink");
set.add("flink");
set.add("bigdata");
set.add("hadoop");
//增强for循环遍历
for (String s:set){
System.out.println(s);
}
}
}
可以看出set集合是无序且不重复的
案例2:存储自定义对象并遍历
首先封装一个学生类 ( 封装 )
public class Student {
private String name;
private int age;
//无参,有参构造方法
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//get方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
//set方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
//toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
再创建集合存储学生对象遍历
import java.util.HashSet;
import java.util.Set;
public class SetTest2 {
public static void main(String[] args) {
//创建Set集合对象
Set<Student> set = new HashSet<>();
//创建学生对象
Student s1 =new Student("student1",20);
Student s2 =new Student("student2",17);
Student s3 =new Student("student3",22);
Student s4 =new Student("student4",20);
Student s5 =new Student("student1",20);
//向集合添加元素
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
//增强for循环遍历
for (Student s :set){
System.out.println(s.getName()+"-----"+s.getAge());
}
}
}
遍历后发现有重复的对象,这是因为HashSet中add()方法实际上与hashCode()方法和equals()方法有关(可以去看Set集合中add()方法的源码),所以集合会不会去重取决与元素类中有没有重写hashCode()方法与equals()方法,而这里的元素类是Student类,Student类中没有重写这两个方法,所以调用Object父类中的equals()方法,而Object类中的equals()方法比较的是地址值,学生对象都是new出来的,所以地址值肯定不一样,所以集合认为这两个对象不同,就没有去重。
解决方法:在Student类中重写hashCode()与equals()方法
//直接快捷键 Alt+Insert 选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);
}
LinkedHashSet类
HashSet 类实现Set接口,而LinkedHashSet类继承自HashSet 类也实现了Set接口。
线程不安全,效率高
LinkedHashSet类概述
a.元素有序唯一
b.由链表保证元素有序(存储和取出顺序一致)
c.由哈希表保证元素唯一
LinkedHashSet集合案例
例:存储并遍历字符串
import java.util.LinkedHashSet;
public class LinkedHashSetTest1 {
public static void main(String[] args) {
//创建集合对象
LinkedHashSet<String> set = new LinkedHashSet<>();
//向集合添加元素
set.add("java");
set.add("hive");
set.add("java");
set.add("spark");
set.add("bigdata");
//增强for循环遍历
for (String s:set){
System.out.println(s);
}
}
}
TreeSet类
线程不安全的,效率高
TreeSet类概述
元素唯一,两种排序方式:
a.使用元素的自然顺序对元素进行排序 (自然排序:无参构造方法)
b.或者根据创建 set 时提供的 Comparator 进行排序 (比较器排序:有参构造方法)
具体取决于使用的构造方法。
TreeSet是如何保证元素的排序和唯一性的
底层数据结构是红黑树(红黑树是一种自平衡的二叉树)
TreeSet案例
例1:TreeSet集合存储数字
import java.util.TreeSet;
public class TreeSetTest1 {
public static void main(String[] args) {
//创建TreeSet集合对象
TreeSet<Integer> set = new TreeSet<>(); //集合只能存放引用数据类型,不能存放基础数据类型,Integer是int类的封装类
//向集合添加元素
set.add(33);
set.add(2);
set.add(45);
set.add(53);
set.add(2);
set.add(78);
//增强for循环遍历
for (Integer i :set){
System.out.println(i);
}
}
}
可以看出结果中元素是唯一且有序的(这里无参构造走的是自然排序的方法,底层源码中涉及到了Comparable接口,有一个向下转型,而Integer类中实现了Comparable接口,这个接口中有ConparaTo方法,所以遍历有序)
部分源码图片:(根据返回值存放元素位置)
例2:TreeSet存储自定义对象(学生类)
首先封装一个公共的学生类(同上)
再创建TreeSet集合存储对象测试
import java.util.TreeSet;
public class TreeSetTest2 {
public static void main(String[] args) {
//创建集合对象
TreeSet<Student> set = new TreeSet<>();
//创建学生对象
Student s1 =new Student("we2323",23);
Student s2 =new Student("df532",26);
Student s3 =new Student("fg3435",16);
Student s4 =new Student("df532",26);
Student s5 =new Student("dsf24124",20);
//向集合添加元素
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
//遍历
for (Student s:set){
System.out.println(s);
}
}
}
然后就报错了,因为这里也是无参构造创建的集合,走的也是自然排序,而自定义的Student类中并没有实现Comparable接口,更没有向下转型,所以报类型转换异常的错误。
解决方法:修改Student类实现Comparade接口
public class Student implements Comparable<Student> {}
再重写ComparaTo方法,修改返回值,按自定义方法排序,如按年龄排序(根据源码方法实现)
@Override
public int compareTo(Student o) {
//比较年龄大小
int i1=this.age-o.age;
//年龄一样姓名不一定一样
int i2= i1==0?this.name.compareTo(o.name):i1; //三目运算判断当年龄一样时,姓名是否一样,一样直接返回i1,不一样就继续按姓名排序
return i2; //返回值决定元素位置,小于0的放左边,大于0放右边,等于0就重复了去重
}
例3:利用TreeSet创建对象时带参数的构造方法进行比较器排序
有参构造源码部分图片:
(我们继续利用学生类,这次按名字长度为主来排序)
首先封装一个学生类(不用按无参构造中实现Comparable接口了,初始的即可)
public class Student {
private String name;
private int age;
//无参,有参构造方法
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//get方法
public String getName() {
return name;
}
public int getAge() {
return age;
}
//set方法
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
//toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
然后创建一个类去实现Comparator接口,重写compara方法(有参构造了,注意不是Comparable接口了)
import java.util.Comparator;
public class ComparableTest implements Comparator<Student> {
//重写compara方法
@Override
public int compare(Student o1, Student o2) { //这里面自定义你的排序方法,我这里按名字长度为主排序
//先比较名字长度
int i1=o1.getName().length()-o2.getName().length();
//若姓名长度一样,比较姓名内容
int i2= i1==0?o1.getName().compareTo(o2.getName()):i1; //姓名长度不一样直i1赋给i2
//若姓名内容也一样,比较年龄
int i3= i2==0?o1.getAge()-o2.getAge():i2;
return i3;//返回值决定元素位置,小于0的放左边,大于0放右边,等于0就重复了去重
}
}
最后用有参构造创建集合,把自定义的类放进去测试
import java.util.TreeSet;
public class TreeSetTest3 {
public static void main(String[] args) {
//创建自定义类的对象
ComparableTest co = new ComparableTest();
//创建集合
TreeSet<Student> set = new TreeSet<>(co); //把自定义类的对象放进去
//创建学生对象并添加
Student s1 =new Student("qq1224",24);
Student s2 =new Student("qq1224",26);
Student s3 =new Student("rw333",24);
Student s4 =new Student("ree434343",26);
Student s5 =new Student("ere2232",16);
Student s6 =new Student("qq1224",24);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
set.add(s6);
//遍历
for (Student s :set){
System.out.println(s);
}
}
}
可以看到结果已经按名字长度为主排序了,也去重了,这就是比较器排序。
如果以后要你需要写多个比较器排序,这样写工作量就太大了,可以用匿名内部类改进
TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() { //这样创建对象就行了
@Override
public int compare(Student o1, Student o2) { //里面的方法一样
int i1=o1.getName().length()-o2.getName().length();
int i2= i1==0?o1.getName().compareTo(o2.getName()):i1;
int i3 = i2==0?o1.getAge()-o2.getAge():i2;
return i3;
}
});
比较方法跟原来一样,就是直接在构造时new了一个类实现Comparator接口。