参考资料:java入门到飞起
Java;泛型;Set 集合;TreeSet;HashSet;数据结构
一、引言
在 Java 编程中,集合框架是一个重要的组成部分,它提供了丰富的数据结构和算法来存储和操作数据。泛型与 Set 集合及其实现类在 Java 集合框架中占据着关键位置,对于提高代码的类型安全性、优化数据存储和检索具有重要意义。
二、泛型
2.1 泛型概述
2.1.1 泛型的介绍
泛型是 JDK 5 中引入的重要特性,它为 Java 语言增添了编译时类型安全检测机制。通过使用泛型,程序员可以在编译阶段就发现类型不匹配的错误,而不是在运行时才出现难以调试的错误,从而提高了程序的稳定性和可靠性。
2.1.2 泛型的好处
- 提前检测问题:将原本在运行时期可能出现的类型问题提前到编译期间,大大降低了运行时错误的发生概率,提高了程序的健壮性。
- 避免强制类型转换:使用泛型后,代码在获取和操作集合元素时无需进行显式的强制类型转换,使代码更加简洁、易读,同时也减少了因强制类型转换不当而引发的ClassCastException异常。
2.1.3 泛型的定义格式
- 单类型格式:<类型>用于指定一种类型,尖括号内通常使用单个字母表示,常见的如<E>(表示元素类型,Element 的缩写)、<T>(表示一般类型,Type 的缩写)。例如,List<E>表示一个存储E类型元素的列表。
- 多类型格式:<类型1,类型2…>用于指定多种类型,不同类型之间用逗号隔开,如<E,T>或<K,V>(常用于表示键值对中的键类型K和值类型V)。例如,Map<K, V>表示一个存储键值对的映射,其中键的类型为K,值的类型为V。
三、Set 集合
3.1Set 集合概述和特点【应用】
Set 集合是 Java 集合框架中的一种重要类型,具有以下显著特点:
- 不允许重复元素:Set 集合中不能存储重复的元素,这使得 Set 集合在需要确保元素唯一性的场景中非常有用,例如去重操作。
- 无索引:Set 集合没有索引,这意味着无法像列表那样通过索引来访问元素,也不能使用普通的 for 循环进行遍历。
3.2Set 集合的使用【应用】
以下代码展示了如何使用 Set 集合存储字符串并进行遍历:
public class MySet1 {
    public static void main(String[] args) {
        //创建集合对象
        Set<String> set = new TreeSet<>();
        //添加元素
        set.add("ccc");
        set.add("aaa");
        set.add("aaa");
        set.add("bbb");
        //Set集合是没有索引的,所以不能使用通过索引获取元素的方法
        //遍历集合
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            String s = it.next();
            System.out.println(s);
        }
        // 如果需要删除元素,可以使用 iterator.remove()
        // 注意:不能直接使用 set.remove(),否则会抛出 ConcurrentModificationException
        // 示例:删除 "aaa"
        iterator = set.iterator(); // 重新获取迭代器
        while (iterator.hasNext()) {
            String s = iterator.next();
            if (s.equals("aaa")) {
                iterator.remove(); // 安全删除
            }
        }
        System.out.println("-----------------------------------");
        for (String s : set) {
            System.out.println(s);
        }
    }
}在上述代码中,首先创建了一个TreeSet对象(TreeSet是Set接口的一个实现类),然后向集合中添加了一些字符串元素,其中包含重复的元素 “aaa”。通过Iterator迭代器和增强 for 循环两种方式对集合进行遍历,输出集合中的元素。可以看到,重复元素 “aaa” 只出现了一次,体现了 Set 集合不允许重复元素的特性。
Iterator迭代器介绍
Iterator(迭代器)是Java集合框架中的一个重要接口,用于遍历集合中的元素。它提供了一种统一的方式来访问集合元素,而不需要暴露集合的内部表示。核心方法如下:
- boolean hasNext():判断是否还有下一个元素
- E next():返回下一个元素
- void remove():删除上次调用- next()返回的元素(可选操作)
关键点
- Set的迭代器只能单向遍历(- hasNext()+- next()),不能反向遍历。
- 删除元素必须用 iterator.remove(),直接使用set.remove()会抛出ConcurrentModificationException。
- Set 是无序的(除非使用 LinkedHashSet或TreeSet),所以遍历顺序可能与插入顺序不同。
- 增强 for 循环底层也是用 Iterator,但不能在遍历时删除元素(除非用 Iterator)。
四、TreeSet 集合
4.1 TreeSet 集合概述和特点【应用】
TreeSet是Set接口的一个实现类,除了具备 Set 集合的基本特点(不允许重复元素、无索引)外,还具有以下特性:
- 元素排序:TreeSet可以将存储的元素按照特定规则进行排序。
- 自然排序:通过TreeSet()无参构造方法创建的集合,会根据元素的自然排序进行排序。所谓自然排序,是指元素所属的类实现Comparable接口,并在其中定义比较规则。
- 比较器排序:通过TreeSet(Comparator comparator)带参构造方法创建的集合,会根据指定的比较器进行排序。比较器是一个实现了Comparator接口的对象,在其compare(T o1, T o2)方法中定义元素的比较规则。
4.2 TreeSet 集合基本使用【应用】
以下代码展示了如何使用TreeSet集合存储Integer类型的整数并进行遍历:
public class TreeSetDemo01 {
    public static void main(String[] args) {
        //创建集合对象
        TreeSet<Integer> ts = new TreeSet<Integer>();
        //添加元素
        ts.add(10);
        ts.add(40);
        ts.add(30);
        ts.add(50);
        ts.add(20);
        ts.add(30);
        //遍历集合
        for (Integer i : ts) {
            System.out.println(i);
        }
    }
}在上述代码中,创建了一个TreeSet集合对象ts,并向其中添加了一些Integer类型的整数,包括重复的数字 30。通过增强 for 循环遍历集合时,输出的元素按照从小到大的顺序排列,体现了TreeSet集合的排序特性,并且重复元素只保留了一个。
4.3 自然排序 Comparable 的使用【应用】
4.3.1 案例需求
存储学生对象并遍历,使用TreeSet集合的无参构造方法,要求按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序。
4.3.2 实现步骤
- 创建 TreeSet 集合:使用空参构造创建TreeSet集合,因为无参构造方法使用自然排序对元素进行排序。
- 实现 Comparable 接口:自定义的Student类需要实现Comparable接口,通过重写compareTo(T o)方法来定义元素的比较规则。
- 重写 compareTo 方法:在重写方法时,要按照指定的主要条件(年龄从小到大)和次要条件(年龄相同时按姓名字母顺序)编写比较逻辑。
4.3.3 代码实现
学生类:
快速构建构造方法:

快捷键:Alt+Insert
public class Student implements Comparable<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 String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    @Override
    public int compareTo(Student o) {
        //按照对象的年龄进行排序
        //主要判断条件: 按照年龄从小到大排序
        int result = this.age - o.age;
        //次要判断条件: 年龄相同时,按照姓名的字母顺序排序
        result = result == 0? this.name.compareTo(o.getName()) : result;
        return result;
    }
}PS:Comparable是干嘛的?
Comparable 是 Java 提供的一个 泛型接口,定义在 java.lang 包中,它只有一个方法:
public interface Comparable<T> {
    int compareTo(T o);  // 比较当前对象与参数对象 o 的大小
}为什么 Student 类要实现 Comparable<Student>?
如果 Student 类实现了 Comparable<Student>,那么:
- 可以调用 Collections.sort()或Arrays.sort()对Student对象列表进行排序:
List<Student> students = new ArrayList<>();
// 添加学生对象...
Collections.sort(students);  // 自动调用 compareTo 方法排序- 可以在 TreeSet或TreeMap中自动排序(因为它们依赖Comparable或Comparator进行排序)。
- 可以方便地进行对象比较,例如在 if (student1.compareTo(student2) > 0)这样的逻辑中使用。
如何实现 Comparable<Student>?
你需要在 Student 类中 重写 compareTo() 方法,定义如何比较两个 Student 对象。例如:
public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private double score;
    // 构造方法、getter/setter 省略...
    @Override
    public int compareTo(Student other) {
        // 按分数升序排序
        return Double.compare(this.score, other.score);
        
        // 或者按姓名升序排序(字符串比较)
        // return this.name.compareTo(other.name);
        
        // 或者按年龄降序排序(注意负号)
        // return Integer.compare(other.age, this.age);
    }
}示例说明:
- Double.compare(this.score, other.score):比较两个学生的分数(升序)。
- this.name.compareTo(other.name):按姓名字典序排序(升序)。
- Integer.compare(other.age, this.age):按年龄降序排序(因为- other.age - this.age是降序逻辑)。
测试类:
public class MyTreeSet2 {
    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);
        //遍历集合
        for (Student student : ts) {
            System.out.println(student);
        }
    }
}在上述代码中,Student类实现了Comparable接口,并在compareTo方法中定义了排序规则。在测试类中,创建了TreeSet集合并添加了多个学生对象,遍历集合时,学生对象按照年龄从小到大排序,年龄相同的按照姓名字母顺序排序。
4.4 比较器排序 Comparator 的使用【应用】
4.4.1 案例需求
存储老师对象并遍历,创建TreeSet集合使用带参构造方法,要求按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序。
4.4.2 实现步骤
- 使用带参构造创建 TreeSet 集合:TreeSet集合的带参构造方法使用比较器排序对元素进行排序。
- 实现 Comparator 接口:让集合构造方法接收一个实现了Comparator接口的对象,在其compare(T o1, T o2)方法中定义元素的比较规则。
- 重写 compare 方法:按照指定的主要条件和次要条件编写比较逻辑。
4.4.3 代码实现
老师类:
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 class MyTreeSet4 {
    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();
                //次要条件
                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);
        //遍历集合
        for (Teacher teacher : ts) {
            System.out.println(teacher);
        }
    }
}在上述代码中,测试类通过TreeSet的带参构造方法传入一个匿名内部类,该内部类实现了Comparator接口,并在compare方法中定义了老师对象的比较规则。在创建老师对象并添加到集合后,遍历集合时老师对象按照年龄和姓名的指定规则进行排序。
4.5 两种比较方式总结
- 自然排序:自定义类实现Comparable接口,重写compareTo方法,集合根据该方法的返回值进行排序。这种方式适用于类本身具有自然的比较顺序,并且在多个地方都需要使用相同排序规则的情况。
- 比较器排序:创建TreeSet对象时传递一个实现了Comparator接口的对象,重写compare方法,集合依据此方法的返回值进行排序。当自然排序不能满足特定需求,或者需要针对不同场景使用不同排序规则时,比较器排序更为灵活。
- 使用选择:在实际使用中,通常优先考虑自然排序。当自然排序无法满足需求时,必须使用比较器排序来实现自定义的排序逻辑。
 
 
                     
            
        













 
                    

 
                 
                    