Java SE 学习笔记(十)—— 集合(2)

1 Set 集合

1.1 Set 集合概述


在这里插入图片描述

Set 集合

  • 不可以存储重复元素(可以去除重复)
  • 存取顺序不一致
  • 没有索引,不能使用普通 for 循环遍历,也不能通过索引来获取、删除 Set 集合里面的元素

Set系列集合:添加的元素无序、不可重复、无索引

  • HashSet:添加的元素无序、不可重复、无索引;
  • LinkedHashSet: 添加的元素 有序、不可重复、无索引
  • TreeSet: 按照大小默认升序排序、不可重复、无索引

1.2 Set 集合的使用

Set 集合的 API 就是直接继承 Collection 的API

存储字符串并遍历

public static void main(String[] args) {
    //Set<String> set = new HashSet<>();
    //Set<String> set = new LinkedHashSet<>();
    Set<String> set = new TreeSet<>();
    set.add("ccc");
    set.add("aaa");
    set.add("aaa");
    set.add("bbb");
    System.out.println(set); // [aaa, bbb, ccc]

    Iterator<String> it = set.iterator();
    while(it.hasNext()){
        System.out.print(it.next()+" "); // aaa bbb ccc
    }

    for (String s : set) {
        System.out.print(s+"*"); // aaa*bbb*ccc*
    }
}

2 TreeSet 类

2.1 TreeSet 类概述


TreeSet 类

  • 底层是红黑树,必须给定排序规则,将元素按照规则进行排序
    • TreeSet():根据其元素的自然排序进行排序
      • 数值类型的数据,默认按照大小升序排序
      • 对于字符串类型,默认按照首字符的编号升序排序
    • TreeSet(Comparator comparator):根据指定的比较器进行排序
      • 对于自定义类型的对象,无法直接排序 (必须制定排序规则)
  • 不可以存储重复元素
  • 没有索引

2.2 TreeSet 基本使用


1️⃣ 存储 Integer 类型的整数并遍历

public static void main(String[] args) {
    TreeSet<Integer> ts = new TreeSet<>();
    ts.add(5);
    ts.add(3);
    ts.add(4);
    ts.add(1);
    ts.add(2);

    System.out.println(ts); // [1, 2, 3, 4, 5]
}

2️⃣ 存储学生对象并遍历

已存在标准的学生类

测试类如下:

import java.util.TreeSet;

public class Demo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();

        Student s1 = new Student("zhangsan",28);
        Student s2 = new Student("lisi",27);
        Student s3 = new Student("wangwu",29);
        Student s4 = new Student("zhaoliu",28);
        Student s5 = new Student("qianqi",30);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        System.out.println(ts);
    }
}

运行测试类会报错,因为不知道按照什么规则(身高、年龄……)对学生对象排序。由此可得,想要使用TreeSet,需要指定排序规则。

2.3 TreeSet 排序

2.3.1 自然排序


自然排序:即让自定义类实现 Comparable接口重写里面的 compareTo方法来制定比较规则。

🙋举个栗子:

案例需求:

  • 存储学生对象并遍历,创建 TreeSet集合使用无参构造方法
  • 要求:按照年龄从小到大排序,如果年龄一样,则按照姓名首字母排序;如果姓名和年龄都一样,才认为是同一个学生对象,不存入。

实现步骤:

  • 使用空参构造创建 TreeSet 集合
    • TreeSet 集合存储自定义对象,无参构造方法 使用的是自然排序对元素进行排序的
  • 自定义的Student类实现 Comparable接口
    • 自然排序,就是让元素所属的类实现Comparable接口,重写 compareTo(T o)方法
  • 重写接口中的compareTo方法
    • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

示例代码:

学生类

public class Student implements Comparable<Student>{ // Comparable接口有泛型且要跟集合的数据类型保持一致
    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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

/*    
    // 只按照年龄升序排序
    @Override
    public int compareTo(Student o) {
//        return this.age - o.age; // 去除了年龄有重复的学生数据
        return this.age - o.age >= 0 ? 1 : -1; // 没有去除年龄有重复的学生数据
    }*/

    @Override
    public int compareTo(Student o) {
        //按照对象的年龄进行排序
        //主要判断条件(年龄)
        int result = this.age - o.age; // this表示当前正在存的元素,o表示已存的元素
        //次要判断条件(名字的字母序)
        result = result == 0 ? this.name.compareTo(o.getName()) : result;
        return result;
    }


	
}

测试类

import java.util.TreeSet;

public class Demo {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();

        Student s1 = new Student("zhangsan",28);
        Student s2 = new Student("lisi",27);
        Student s3 = new Student("wangwu",29);
        Student s4 = new Student("zhaoliu",28);
        Student s5 = new Student("qianqi",30);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);

        System.out.println(ts);
        // 年龄28有重复,去除s4的数据
        // [Student{name='lisi', age=27}, Student{name='zhangsan', age=28}, Student{name='wangwu', age=29}, Student{name='qianqi', age=30}]

        // 按照年龄升序排序的同时,不想不想去除年龄重复的学生数据
        // [Student{name='lisi', age=27}, Student{name='zhangsan', age=28}, Student{name='zhaoliu', age=28}, Student{name='wangwu', age=29}, Student{name='qianqi', age=30}]
    }
}

在这里插入图片描述

❗️注意:以下两个compareTo方法是不一样的

  • 第二个compareTo方法是字符串的方法
public static void main(String[] args) {
	String s1 = "aaa";
	String s2 = "ab";

	System.out.println(s1.compareTo(s2)); // -1
	//首先比较第一个字母,如果第一个字母是一样的,那么继续比较后面的字母
	//当不一样的时候,就拿着对应的码表值97,减去 b的码表值 98
	//认为a是比b要小的。
}

2.3.2 比较器排序


比较器排序:即TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来制定比较规则。

🙋举个栗子:

案例需求:

  • 存储老师对象并遍历,创建TreeSet集合使用带参构造方法
  • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序

实现步骤:

  • 用TreeSet集合存储自定义对象,带参构造方法 使用的是比较器排序对元素进行排序的
  • 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1,T o2)方法
  • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写

示例代码:

老师类

public class Teacher {
    private String name;
    private int age;

    public Teacher() {
    }

    public Teacher(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;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试类

public static void main(String[] args) {
	TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
		@Override
		public int compare(Teacher o1, Teacher o2) {
			//o1表示现在要存入的那个元素
			//o2表示已经存入到集合中的元素

			//主要条件
			int result = o1.getAge() - o2.getAge(); // 升序
			//int result = o2.getAge() - o1.getAge(); // 降序

			//次要条件
			result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
			return result;
		}
	});

	Teacher t1 = new Teacher("zhangsan",23);
	Teacher t2 = new Teacher("lisi",22);
	Teacher t3 = new Teacher("wangwu",24);
	Teacher t4 = new Teacher("zhaoliu",24);

	ts.add(t1);
	ts.add(t2);
	ts.add(t3);
	ts.add(t4);

	System.out.println(ts);
}

❗️注意:

  • 对于浮点数的排序比较,例如按照身高升序排序: return Double.compare(o1.getHeight-o2.getHeight);

2.3.3 两种排序方式总结


🌈 两种排序方式总结:

  • 自然排序:自定义类实现 Comparable 接口,重写 compareTo 方法,根据返回值进行排序
  • 比较器排序:创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序

❗️注意:

  • 如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器

🎠 两种方式中关于返回值的规则:

  • 如果返回值为负数,表示当前存入的元素是较小值,存左边
  • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
  • 如果返回值为正数,表示当前存入的元素是较大值,存右边

案例:按照字符串长短排序

要求:存入4个字符串“c”、“ab”、“df”、“qwer”,按照长度排序,如果一样长则按照首字母排序。

请自行选择比较器排序和自然排序两种方式。

示例代码:

public static void main(String[] args) {
//        TreeSet<String> ts = new TreeSet<>();
//        ts.add("c");
//        ts.add("ab");
//        ts.add("df");
//        ts.add("qwer");
//        System.out.println(ts); // [ab, c, df, qwer]

    /*
     * 通过查看String源码可知,String类已经实现了Comparable接口,
     * 即String类的自然排序规则Java内部已经帮我们写好了,且默认规则为按照字母字典序排列
     * 所以我们应该选择比较器排序
     * */

    // 1. 匿名内部类方式
//        TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
//            @Override
//            public int compare(String o1, String o2) {
//                int result = o1.length() - o2.length();
//                result = result==0?o1.compareTo(o2):result;
//                return result;
//            }
//        });

    // 2. lambda函数方式
    TreeSet<String> ts = new TreeSet<>(
            (String o1, String o2)->{
                int result = o1.length() - o2.length();
                result = result == 0 ? o1.compareTo(o2) : result;
                return result;
            }
    );


    ts.add("c");
    ts.add("ab");
    ts.add("df");
    ts.add("qwer");
    System.out.println(ts); // [ab, c, df, qwer]
}

3 HashSet 类

3.1 HashSet 类概述


HashSet类

  • 底层数据结构是 哈希表
  • 不能保证存储和取出的顺序完全一致
  • 是 Set 集合,元素唯一,不可以存储重复元素
  • 没有索引,不能使用普通 for 循环遍历

3.2 HashSet 基本使用


存储字符串并遍历

public static void main(String[] args) {
	HashSet<String> hs = new HashSet<>();

	hs.add("hello");
	hs.add("world");
	hs.add("java");
	hs.add("java");
	hs.add("java");
	hs.add("java");
	hs.add("java");
	hs.add("java");

	Iterator<String> it = hs.iterator();
	while(it.hasNext()){
		String s = it.next();
		System.out.println(s);
	}
	System.out.println("=============================");

	for (String s : hs) {
		System.out.println(s);
	}
}

3.3 哈希值


哈希值(哈希码值):

  • 是JDK根据对象的 地址 或者 属性值,算出来的 int 类型的整数

哈希值的特点:

  • 如果没有重写hashCode() 方法,那么是根据对象的地址值计算出的哈希值。同一个对象多次调用 hashCode() 方法返回的哈希值是相同的,不同对象的哈希值是不同的
  • 而重写 hashCode() 方法,一般都是通过对象的属性值计算出哈希值。如果不同的对象属性值是一样的,那么计算出的哈希值也是一样的
    • Alt+Insert选择equals() and hashCode(),默认选项直接 next,自动生成重写方法

获取哈希值:

  • Object类中的 public int hashCode():返回对象的哈希码值

示例代码:

学生类

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;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }


    //我们可以对Object类中的hashCode方法进行重写
    //在重写之后,就一般是根据对象的属性值来计算哈希值的。
    //此时跟对象的地址值就没有任何关系了。
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

测试类

import java.util.HashSet;
import java.util.Set;

public class Demo {
    public static void main(String[] args) {

        Set<Student> students=new HashSet<>();
        Student s1 = new Student("xiaozhi",23);
        Student s2 = new Student("xiaomei",22);

        // 1. 同一个对象多次调用hashCode()时其返回值一样
        // 因为在Object类中,是根据对象的地址值计算出来的哈希值。
        System.out.println(s1.hashCode());// 265724891
        System.out.println(s1.hashCode());// 265724891

        // 2. 不同对象的哈希值是不一样的
        System.out.println(s2.hashCode());// 265334724

        Student s3 = new Student("LALA",23);
        Student s4 = new Student("LALA",23);
        System.out.println(s3.hashCode()); // 72199085
        System.out.println(s4.hashCode()); // 72199085

        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        System.out.println(students);
        // [Student{name='LALA', age=23}, Student{name='xiaomei', age=22}, Student{name='xiaozhi', age=23}]
    }
}

4 LinkedHashSet 类


LinkedHashSet 类中的元素有序、不重复、无索引

  • 这里的有序指的是保证存储和取出的元素顺序是一致的

LinkedHashSet 类原理:其底层数据结构依然是哈希表,只是每个元素有额外的多了一个 双链表 机制记录存储的顺序

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值