Java讲课笔记22:Set接口及其实现类

零、本讲学习目标

1、掌握HashSet集合的使用

2、掌握TreeSet集合的使用

一、Set接口

1、Set接口概述

Set接口和List接口一样,同样继承自Collection接口。Set接口中的元素无序,并且都会以某种规则保证存入的元素不出现重复。因此,我们可以利用Set集合的这个特性是实现一组数据的去重工作。

2、Set接口主要实现类

在这里插入图片描述

(1)HashSet集合

根据对象哈希值,确定元素在集合中的存储位置,因此存取和查找性能良好。

(2)TreeSet集合

以二叉树的方式来存储元素,可以实现对集合中的元素进行排序。

二、HashSet集合

1、HashSet集合概述

  • HashSet是Set接口的一个实现类,它所存储的元素不可重复,并且无序。
  • 当向HashSet集合中添加一个元素时,首先会调用该元素的hashCode()方法来确定元素的存储位置,然后再调用元素对象的equals()方法来确保该位置没有重复元素。

2、案例演示:创建与遍历专业哈希集合

在这里插入图片描述

package net.hw.lesson22;

import java.util.HashSet;

/**
 * 功能:创建与遍历专业哈希集合
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Example2201 {
    public static void main(String[] args) {
        // 创建专业哈希集合
        HashSet<String> majors = new HashSet<>();

        // 添加元素(有重复的元素)
        majors.add("计算机应用技术专业");
        majors.add("软件技术专业");
        majors.add("大数据技术专业");
        majors.add("软件技术专业");
        majors.add("人工智能专业");

        // 输出整个集合
        System.out.println("专业集合:" + majors);
        // 遍历输出集合元素
        System.out.print("遍历集合:");
        majors.forEach(major -> System.out.print(major + " "));
    }
}
  • 运行程序,查看结果
    在这里插入图片描述

3、HashSet集合的对象存储原理

在这里插入图片描述

  • 当向集合中存入元素时,为了保证HasheSet正常工作,要求在存入对象时,需要重写Object类中的hashCode()和equals()方法。
  • 在Java中,一些基本数据包装类、String类等都已经默认重写了hashCode()和equals()方法。
  • 开发者向HashSet集合中添加自定义的数据类型,如Student类时,必须增加重写的hashCode()和equals()方法,才能保证数据的唯一性。

在案例Example2201中,将String存入HashSet中,而String类已经默认重写了hashSet()与equals()方法,因此没有任何问题。
在这里插入图片描述
在这里插入图片描述

下面我们来演示将自定义的类型对象存入HashSet,看一看会出现什么样的结果。

4、案例演示:创建学生哈希集合

  • 创建Student类
    在这里插入图片描述
package net.hw.lesson22;

/**
 * 功能:学生类
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Student {
    private String id;
    private String name;

    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
  • 创建Example2202
    在这里插入图片描述
package net.hw.lesson22;

import java.util.HashSet;

/**
 * 功能:演示学生哈希集合
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Example2202 {
    public static void main(String[] args) {
        // 创建学生哈希集合
        HashSet<Student> students = new HashSet<>();

        // 创建学生对象
        Student stu1 = new Student("201901", "李晓红");
        Student stu2 = new Student("201902", "钟文艳");
        Student stu3 = new Student("201903", "郑智化");
        Student stu4 = new Student("201902", "钟文艳"); // 重复记录

        // 将学生对象添加到集合
        students.add(stu1);
        students.add(stu2);
        students.add(stu3);
        students.add(stu4);

        // 遍历输出学生哈希集合
        students.forEach(student -> System.out.println(student));
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
    学生哈希集合之所以没有去除重复元素,是因为Student类没有重写hashCode()与equals()方法,因为stu2与stu4两个对象虽然内容是相同的,但是对象的地址是不同的,因此学生哈希集合就认为它们是不同的元素而存入集合里。

如果我们认为id相同的学生就是同一个学生,那么我们来重写Student类的hashCode()与equals()方法。

  • 修改Student类,重写hashCode()与equals()方法
    在这里插入图片描述
package net.hw.lesson22;

/**
 * 功能:学生类
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Student {
    private String id;
    private String name;

    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

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

    @Override
    public int hashCode() {
        return id.hashCode(); // 返回id属性的哈希值
    }

    @Override
    public boolean equals(Object o) {
        // 判断是否是同一对象
        if (this == o) {
            return true;
        }
        // 判断对象是否是Student类型
        if (o instanceof Student) {
            // 将对象强制转换为Student类型
            Student student = (Student) o;
            // 判断两者的id是否相同
            if (this.id.equals(student.id)) {
                return true;
            }
        }
        return false;
    }
}
  • 运行Example2202,查看结果
    在这里插入图片描述
  • 简要说明:在添加stu4对象时,由于它的id与stu2的id相等,stu2.id.equals(stu4.id)返回true,于是哈希集合认为stu4与stu2两个对象是相等的,于是就舍弃了重复的stu4对象。
  • 大家可以看到,遍历集合输出元素的顺序,跟添加集合元素的顺序并不相同,因为哈希集合本来就是无序的。

三、TreeSet集合

1、TreeSet集合概述

  • TreeSet是Set接口的另一个实现类,它内部采用平衡二叉树来存储元素,来保证TreeSet集合中没有重复的元素,并且可以对元素进行排序。
  • 二叉树就是每个节点最多有两个子节点的有序树,每个节点及其子节点组成的树称为子树,左侧的节点称为“左子树”,右侧的节点称为“右子树”,其中左子树上的元素小于它的根结点,而右子树上的元素大于它的根结点。

2、TreeSet集合的对象存储原理

在这里插入图片描述

(1)结构说明

同一层的元素可分为1个根节点元素和2个子节点元素,左边的元素总是小于右边的元素。

(2)存储原理

  • TreeSet集合没有元素时,新增元素置于二叉树最顶层
  • 接着新增元素时,首先会与根节点元素比较
  • 如果小于根节点元素就与左边的分支比较
  • 如果大于根节点元素就与右边的分支比较
  • 如此往复,直到与最后一个元素进行比较
    – 如果新元素小于最后一个元素,就将其放在最后一个元素的左子树上
    – 如果新元素小于最后一个元素,就将其放在最后一个元素的左子树上

(3)存储过程

在这里插入图片描述

  • 添加元素
    – 向TreeSet中依次添加13、8、17、17、1、11、15、25元素
  • 存储过程
    – 将元素13个放在二叉树的最顶端
    – 之后存入的元素与13比较,如果小于13就将该元素放左子树上,如果大于13,就将该元素放在右子树上
    – 当二叉树中已经存入一个17的元素时,再向集合中存入一个为17的元素时,TreeSet会将重复的元素去掉
    – 以此类推,直到排定最后一个元素

课堂练习:添加23、67、30、78、21、98、56、47到哈希集合,绘制存储数据的二叉树

3、TreeSet集合特有方法

方法声明 功能描述
Object first() 返回TreeSet集合的首个元素
Object last() 返回TreeSet集合的最后一个元素
Object lower(Object o) 返回TreeSet集合中小于给定元素的最大元素,如果没有返回null
Object floor(Object o) 返回TreeSet集合中小于或等于给定元素的最大元素,如果没有返回null
Object higher(Object o) 返回TreeSet集合中大于给定元素的最小元素,如果没有返回null
Object ceiling(Object o) 返回TreeSet集合中大于或等于给定元素的最小元素,如果没有返回null
Object pollFirst() 移除并返回集合的第一个元素
Object pollLast() 移除并返回集合的最后一个元素

4、案例演示:创建与操作TreeSet对象

  • 创建Example2203
    在这里插入图片描述
package net.hw.lesson22;

import java.util.TreeSet;

/**
 * 功能:创建与操作TreeSet对象
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Example2203 {
    public static void main(String[] args) {
        // 创建TreeSet集合
        TreeSet<Integer> nums = new TreeSet<>();

        // 添加元素
        nums.add(5);
        nums.add(13);
        nums.add(8);
        nums.add(17);
        nums.add(11);
        nums.add(25);
        nums.add(15);

        // 输出整个集合
        System.out.println("整个集合:" + nums);

        // 获取首尾元素
        System.out.println("集合首元素:" + nums.first());
        System.out.println("集合尾元素:" + nums.last());

        // 比较获取元素
        System.out.println("大于或等于14的最小元素:" + nums.higher(14));
        System.out.println("大于或等于26的最小元素:" + nums.ceiling(26));
        System.out.println("小于或等于20的最大元素:" + nums.lower(20));
        System.out.println("小于或等于4的最大元素:" + nums.floor(4));

        // 删除首元素
        int first = nums.pollFirst();
        System.out.println("删除首元素[" + first + "]后:" + nums);
        // 删除尾元素
        int last = nums.pollLast();
        System.out.println("删除尾元素[" + last + "]后:" + nums);
    }
}
  • 运行程序,查看结果
    在这里插入图片描述

5、TreeSet集合的元素排序

  • 向TreeSet集合添加元素时,都会调用compareTo()方法进行比较排序,该方法是Comparable接口中定义的,因此要想对集合中的元素进行排序,就必须实现Comparable接口。
  • Java中大部分的类都实现了Comparable接口,并默认实现了接口中的CompareTo()方法,如Integer、Double和String等。
  • 为了解决自定义类型数据没有实现Comparable接口的问题,Java提供了两种TreeSet的排序规则:自然排序和定制排序

6、TreeSet集合的自然排序

(1)实现方法

要求存储的元素类必须实现Comparable接口,并重写compareTo()方法。

(2)案例演示:演示动物TreeSet集合

  • 创建Animal类,实现Comparable接口
    在这里插入图片描述
package net.hw.lesson22;

/**
 * 功能:动物类
 *      实现Comparable接口
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Animal implements Comparable<Animal> {
    private String name;
    private int age;

    public Animal(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 int compareTo(Animal o) {
        if (this.age == o.age) { // 年龄相同比较姓名
            return this.name.compareTo(o.name); // 升序,若要降序,则 o.name.compareTo(this.name);
        } else {
            return this.age - o.age; // 升序,若要降序,则 o.age - this.age
        }
    }

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • 创建Example2204
    在这里插入图片描述
package net.hw.lesson22;

import java.util.TreeSet;

/**
 * 功能:实现动物类集合排序
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Example2204 {
    public static void main(String[] args) {
        // 创建动物集合
        TreeSet<Animal> animals = new TreeSet<>();
        // 创建动物对象
        Animal animal1 = new Animal("欢欢", 4);
        Animal animal2 = new Animal("瑞瑞", 7);
        Animal animal3 = new Animal("灵灵", 3);
        Animal animal4 = new Animal("豆豆", 5);
        Animal animal5 = new Animal("乐乐", 3);
        // 将动物对象添加到集合
        animals.add(animal1);
        animals.add(animal2);
        animals.add(animal3);
        animals.add(animal4);
        animals.add(animal5);
        // 遍历动物集合
        animals.forEach(animal -> System.out.println(animal));
    }
}
  • 运行程序,查看结果
    在这里插入图片描述
  • 简要说明:确实动物集合按照年龄升序排列了,其中“灵灵”和“乐乐”两个动物年龄相同,再按照姓名升序排列,本来“灵灵”先加入集合,“乐乐”后加入集合,但是按照拼音排序法(lingling > lele),所以“乐乐”排在“灵灵”之前了。要改变排序方式,也很简单,修改Animal类的compareTo方法即可。

7、TreeSet集合的定制排序

(1)实现方法

要求自定义一个比较器,该比较器必须实现Comparator接口,并重写compare()方法,然后将该比较器作为参数传入集合的有参构造。

(3)案例演示:创建字符串集合,按照字符串长度排序

  • 创建Example2205
    在这里插入图片描述
package net.hw.lesson22;

import java.util.Comparator;
import java.util.TreeSet;

/**
 * 功能:创建字符串集合,按照字符串长度排序
 * 作者:华卫
 * 日期:2020年05月24日
 */
public class Example2205 {
    public static void main(String[] args) {
        // 1. 创建字符串集合,传入Comparator接口匿名对象
        TreeSet<String> names = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length(); // 按字符串长度升序排列
            }
        });
        // 添加字符串元素
        names.add("Black");
        names.add("Amy");
        names.add("Amanda");
        names.add("Anne");
        // 输出整个集合
        System.out.println("姓名集合(按长度升序):" + names);

        // 2. 创建字符串集合,使用Lambda表达式定制排序规则
        TreeSet<String> words = new TreeSet<>((o1, o2) -> {return o2.length() - o1.length();});
        // 添加字符串元素
        words.add("天长地久");
        words.add("爱");
        words.add("勿忘我");
        words.add("光明");
        // 输出整个集合
        System.out.println("词语集合(按长度降序):" + words);
    }
}
  • 运行程序,查看结果
    在这里插入图片描述

8、TreeSet集合两种排序的区别

(1)自然排序

  • 元素类本身实现Comparable接口
  • 依赖compareTo()方法的实现
  • 实现Comparable接口排序规则比较单一,不利于后续改进

(2)定制排序

  • 适合元素类本身未实现Comparable接口,无法进行比较
  • 适合元素类实现的Comparable接口排序规则无法满足用户需求
  • 会额外定义一个实现Comparator接口的比较器

四、课后作业

任务1、完成IP地址去重工作

去掉重复IP地址,统计不同IP地址的个数。

192.168.234.21
192.168.234.22
192.168.234.21
192.168.234.21
192.168.234.23
192.168.234.21
192.168.234.21
192.168.234.21
192.168.234.25
192.168.234.21
192.168.234.21
192.168.234.26
192.168.234.21
192.168.234.27
192.168.234.21
192.168.234.27
192.168.234.21
192.168.234.29
192.168.234.21
192.168.234.26
192.168.234.21
192.168.234.25
192.168.234.25
192.168.234.21
192.168.234.22
192.168.234.21

运行结果如下:
在这里插入图片描述

任务2、字符串中字符出现频率统计

输入任意字符串,统计有多少个不同字符,每种字符的个数,以及每种字符串出现的频率。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/howard2005/article/details/106309867