Java集合排序

本文详细介绍了Java中集合排序的基本概念、Collections.sort()方法、Comparable接口的使用以及Comparator接口的灵活应用。同时,着重讲解了自动排序的TreeMap和TreeSet,它们基于红黑树实现,提供了高效的有序数据操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 集合排序API

1.1 集合排序概述

        集合排序是指对一个集合中的元素按照特定规则进行重新排列,以使得集合中的元素按照预定义的顺序呈现。

        在集合排序中,通常需要定义一个比较规则,这个比较规则用于决定集合中的元素在排序后的顺序。元素之间的比较可以是数字的大小比较、字符串的字典序比较、对象的属性比较等。

        例如,将学生信息集合按照学生的学号排序,按照姓名的字典顺序排序,或者按照生日排序。

        在Java中实现集合排序的方式可以分为两大类:

        1、使用集合排序API。

        2、使用支持自动排序的集合。

  • 一些集合底层使用的数据结构支持自动排序,如红黑树结构

1.2 Collections.sort() 方法

        Collections是集合的工具类,它提供了很多便于我们操作集合的方法,其中就有用于集合排序的sort方法。该方法的定义为:

void sort(List<T> list)

        该方法的作用是对集合元素进行自然排序(按照元素的由小至大的顺序)。

        编写代码,测试集合的排序实现。代码示意如下:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class ListSortDemo1 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<Integer>();
        Random r = new Random(1);
        for (int i = 0; i < 10; i++) {
            list.add(r.nextInt(100));
        }
        // [85, 88, 47, 13, 54, 4, 34, 6, 78, 48]
        System.out.println(list);
        Collections.sort(list);
        // [4, 6, 13, 34, 47, 48, 54, 78, 85, 88]
        System.out.println(list);
    }
} 

1.3 Comparable接口

        在Java中,如果想对某个集合的元素进行排序,有一个前提条件:该集合中的元素必须是Comparable接口的实现类。

        Comparable是一个接口,用于定义其子类是可以比较的,该接口有一个用于比较大小的抽象方法:

        所有 Comparable 接口的实现类都需要重写 compareTo 方法来定义对象间的比较规则:

int compareTo(T t);  

        此方法使用当前对象与给定对象进行比较,并要求返回一个整数,这个整数不关心具体的值,而是关注取值范围:

  • 当返回值>0时,表示当前对象比参数给定的对象大
  • 当返回值<0时,表示当前对象比参数给定的对象小
  • 当返回值=0时,表示当前对象和参数给定的对象相等

        编写代码,定义类并实现Comparable接口;然后定义包含该对象的集合,测试其排序效果。代码示意如下:

public class Student implements Comparable<Student>{
    String String name;
    String int age;
    String double score;
    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    @Override
    public int compareTo(Student o) {
        // 按年龄的大小排序
        return this.age - o.age;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

import java.util.*;
public class ListSortDemo2 {
    public static void main(String[] args) {
        Student s1 = new Student("Tom", 18, 88.5);
        Student s2 = new Student("Jerry", 16, 95);
        Student s3 = new Student("Lucy", 17, 100);
        System.out.println("s1 compareTo s2:" + s1.compareTo(s2));
        System.out.println("s2 compareTo s3:" + s2.compareTo(s3));
        List<Student> list = Arrays.asList(s1,s2,s3);
        // 排序
        Collections.sort(list);
        // 查看list中的元素
        for(Student s : list){
            System.out.println(s); // Jerry, Lucy, Tom
        }
    }
}

1.4 Comparator接口

        一旦Java类实现了 Comparable 接口,其比较逻辑就已经确定;如果希望在排序的操作中临时指定比较规则,可以通过声明 Comparator 接口的实现类来实现。

        Comparator 接口也用于定义比较逻辑,可用于在集合外部提供元素的比较逻辑。对比如下图所示:

        因此,实现了 Comparator 接口的类可以看作是定义了特定比较逻辑的比较器。

        Comparator接口的核心方法是compare方法,用于比较两个元素的大小:

int compare(T o1,T o2)

        实现compare方法的返回值要求:

  • 若o1>o2则返回值应>0
  • 若o1<o2则返回值应<0
  • 若o1=o2则返回值应为0

        接续上一个案例:使用Comparator接口,为sort() 方法指定其他比较规则并实现排序。代码示意如下:

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ListSortDemo3 {
    public static void main(String[] args) {
        Student s1 = new Student("Tom", 18, 88.5);
        Student s2 = new Student("Jerry", 16, 95);
        Student s3 = new Student("Lucy", 17, 100);
        List<Student> list = Arrays.asList(s1,s2,s3);
        // 排序 指定新的比较逻辑 按分数排序
        System.out.println("====> 按分数排序后的结果");
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return (int) Math.ceil(o1.score - o2.score);
            }
        });
        // 查看list中的元素
        for(Student s : list){
            System.out.println(s); // Tom, Jerry, Lucy
        }

        // 排序 指定新的比较逻辑 按分数降序排序
        System.out.println("====> 按分数降序排序后的结果");
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return (int) Math.ceil(o2.score - o1.score);
            }
        });
        // 查看list中的元素
        for(Student s : list){
            System.out.println(s); // Lucy, Jerry, Tom
        }

        // 排序 指定新的比较逻辑 按姓名排列
        System.out.println("====> 按姓名排序后的结果");
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // String和包装类都实现了Comparable接口
                return o1.name.compareTo(o2.name);
            }
        });
        // 查看list中的元素
        for(Student s : list){
            System.out.println(s); // Jerry, Lucy, Tom
        }
    }
}

1.5 Comparator接口中的默认方法

        在Java 8之前,接口中只能定义抽象方法,也就是只能定义方法的签名,而没有具体的实现。

        Java 8引入了默认方法(Default Method)的概念,它是一种可以在接口中定义具体实现的方法。默认方法的引入使得Java的接口可以更好地支持类库的演化和功能的扩展。

        Comparator 接口在Java 8及以后的版本中引入了一些默认方法,这些方法提供了更多的灵活性和便利性。

        以下是Comparator 接口中常用的默认方法:

        1、reversed():该方法返回当前比较器的逆序比较器。它将原来的比较规则进行颠倒,使得升序排序变为降序排序,反之亦然。

        2、thenComparing(Comparator<? super T> other):该方法返回一个组合比较器,用于对两个比较规则进行联合排序。如果原始比较器认为两个元素相等,则使用传入的 other 比较器进一步比较。

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class ListSortDemo4 {
    public static void main(String[] args) {
        Student s1 = new Student("Tom", 18, 88.5);
        Student s2 = new Student("Jerry", 16, 95);
        Student s3 = new Student("Lucy", 17, 100);
        Student s4 = new Student("Alice", 17, 96);
        List<Student> list = Arrays.asList(s1,s2,s3,s4);
        // 声明年龄升序比较器
        Comparator<Student> c1 = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.age - o2.age;
            }
        };
        // 声明年龄降序比较器
        Comparator<Student> c2 = c1.reversed();
        // 测试效果
        System.out.println("====> 按年龄升序排序后的结果");
        Collections.sort(list, c1);
        printList(list);
        System.out.println("====> 按年龄降序排序后的结果");
        Collections.sort(list, c2);
        printList(list);
        // 声明分数升序比较器
        Comparator<Student> c3 = new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return (int) Math.ceil(o1.score - o2.score);
            }
        };
        // 组合比较器:先按年龄降序,年龄相同按分数升序
        Comparator<Student> c4 = c2.thenComparing(c3);
        // 测试效果
        System.out.println("====> 按年龄降序,年龄相同按分数升序排序后的结果");
        Collections.sort(list, c4);
        printList(list);
    }

    public static void printList(List<Student> list) {
        for(Student s : list){
            System.out.println(s);
        }
    }
}

2. 自动排序的集合

2.1 树数据结构

        树(Tree)是一种常见的非线性数据结构,它由一组节点(Node)和节点之间的连接关系(边,Edge)组成。树的结构类似于自然界中的树,由根节点、分支节点和叶子节点构成,分支节点连接多个子节点,而叶子节点没有子节点。

2.2 二叉搜索树

        二叉搜索树(Binary Search Tree, BST)满足以下条件:

        1、对于根节点,左子树中所有节点的值 < 根节点的值 < 右子树中所有节点的值

        2、任意节点的左、右子树也是二叉搜索树,即同样满足条件 1

        这个特性使得在二叉搜索树中进行查找操作非常高效。在理想的情况下,二叉搜索树是“平衡”的,这样就可以在logn轮循环内查找任意节点。

        BST的插入和删除操作也相对简单,但它没有强制性的自平衡机制,可能导致树的不平衡性,如果二叉树退化成链表,这时各种操作的时间复杂度也会退化为O(n)。

2.3 红黑树

        红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在普通二叉搜索树的基础上添加了额外的规则来保持树的平衡。红黑树的命名源自于每个节点都有一个颜色属性,可以是红色或黑色。

        红黑树通过遵守以下五条规则来保持树的平衡性:

  • 每个节点要么是红色,要么是黑色
  • 根节点是黑色
  • 每个叶子节点(NIL节点)都是黑色
  • 如果一个节点是红色的,则其两个子节点必须都是黑色的
  • 从任意节点到其每个叶子节点的简单路径上,黑色节点的数量相同

        示意如下:

2.4 TreeMap

        在Java中,TreeMap 是一种实现了 SortedMap 接口的有序映射集合。它基于红黑树(Red-Black Tree)数据结构来实现,可以确保其中的元素按照键的自然顺序或者自定义比较器进行排序。TreeMap 提供了一系列方法来操作键值对,具有快速查找、插入和删除的特性。

        以下是 TreeMap 的一些特点和用法:

        1、键的有序性:TreeMap 中的键是有序的,这是因为它基于红黑树来实现。键的排序可以是键类型的自然顺序,或者通过传入的 Comparator 对象来定义。

        2、查找效率:由于红黑树是一种自平衡的二叉搜索树,TreeMap 中的查找、插入和删除操作的时间复杂度都是 O(log n),其中 n 是映射中键值对的数量。

        3、允许 null 键:TreeMap 允许 null 键,但要注意在自定义比较器中处理 null 键的情况,否则可能导致异常。

        TreeMap充分发挥了二叉搜索树的特点,为用户提供了一些与Key元素大小相关的方法。

        编写代码,测试TreeMap的使用。代码示意如下:

import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

public class TreeMapDemo {
    public static void main(String[] args) {
        TreeMap<Integer, String> treeMap = new TreeMap();
        treeMap.put(5,"Tom");
        treeMap.put(3,"Jerry");
        treeMap.put(9,"Lucy");
        treeMap.put(2,"Tony");
        // 遍历TreeMap
        for(Map.Entry<Integer, String> entry : treeMap.entrySet()){
            System.out.println("key: " + entry.getKey()+", value: " + entry.getValue());
        }
        System.out.println("------------");
        // 返回临近的高值键值对
        Map.Entry<Integer,String> entry1 = treeMap.higherEntry(8);
        System.out.println("higherEntry(8): "+entry1);
        // 返回临近的低值键值对
        Map.Entry<Integer,String> entry2 = treeMap.lowerEntry(8);
        System.out.println("lowerEntry(8): "+entry2);
        System.out.println("------------");
        // 获取子集 key在 from和to之间,默认包前不包后
        SortedMap<Integer,String> subMap1 = treeMap.subMap(3,5);
        System.out.println("subMap(3, 5): "+subMap1);
        // 可设置是否包含边界
        System.out.println("------------");
        System.out.println("subMap(3, 5) include: "+treeMap.subMap(3, true, 9, true));
        // 返回逆序的集合
        System.out.println("------------");
        System.out.println("desc: "+treeMap.descendingMap());
    }
}

2.5 TreeSet

        在Java中,TreeSet 是一种实现了 SortedSet 接口的有序集合。它底层使用一个TreeMap的Key来存储所有的元素。TreeSet 中不允许包含重复的元素。

        编写代码,测试TreeSet的使用。代码示意如下:

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

public class TreeSetDemo {
    public static void main(String[] args) {
        Employee e1 = new Employee("Tom",18);
        Employee e2 = new Employee("Jerry",16);
        Employee e3 = new Employee("Lucy",13);
        Employee e4 = new Employee("Tony",20);
        TreeSet<Employee> set1 = new TreeSet();
        set1.add(e1);
        set1.add(e2);
        set1.add(e3);
        set1.add(e4);
        System.out.println("set1: "+set1);
        Comparator<Employee> cpt = new Comparator<Employee>() {
            @Override
            public int compare(Employee o1, Employee o2) {
                return o1.name.compareTo(o2.name);
            }
        };
        TreeSet<Employee> set2 = new TreeSet(cpt);
        set2.addAll(set1);
        System.out.println("set2: "+set2);
    }
}

class Employee implements Comparable<Employee> {
    String name;
    int age;
    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public int compareTo(Employee o) {
        System.out.println(this.age + " compareTo" + o.age );
        return this.age - o.age;
    }
    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

3. 总结

        1、集合排序是指对一个集合中的元素按照特定规则进行重新排列,以使得集合中的元素按照预定义的顺序呈现。在Java中实现集合排序的方式可以分为两大类。

  • 使用集合排序API
  • 使用支持自动排序的集合

        2、常用的集合排序API包括以下几种。

  • Collections.sort() 方法
  • Comparable接口
  • Comparator接口

        3、TreeMap 是一种实现了 SortedMap 接口的有序映射集合。它基于红黑树(Red-Black Tree)数据结构来实现,可以确保其中的元素按照键的自然顺序或者自定义比较器进行排序。

        4、TreeSet 是一种实现了 SortedSet 接口的有序集合。它底层使用一个TreeMap的Key来存储所有的元素。

Java集合的自定义排序可以使用 Comparator 或 Comparable 接口来实现。 如果使用 Comparator 接口,你可以创建一个实现了 Comparator 接口的类,并重写 compare 方法来定义排序规则。然后通过 Collections.sort 方法使用自定义的 Comparator 对象来对集合进行排序。例如,在引用中的示例中,我们创建了一个 Mycomparator 类来按照 Person 对象的年龄进行排序,并将其传递给 Collections.sort 方法来排序 ArrayList。 如果使用 Comparable 接口,你可以在对象类中实现 Comparable 接口,并重写 compareTo 方法来定义排序规则。然后通过 Collections.sort 方法来对集合进行排序。例如,在引用中的示例中,我们在 Person 类中实现了 Comparable 接口,并重写了 compareTo 方法来按照年龄从大到小进行排序。 另外,在 JDK 8 之后,还可以使用 Stream 流来实现排序功能。你可以使用 sorted 方法和 Comparator.comparing 方法来对集合进行排序。例如,在引用中的示例中,我们使用 Stream 排序对包含 Person 对象的 List 进行排序。 以上是几种常见的自定义排序方法,你可以根据具体需求选择适合的方法来实现自定义排序。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java集合框架实现自定义排序](https://blog.youkuaiyun.com/weixin_34066347/article/details/86114722)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Java 中 List 排序的 3 种方法](https://blog.youkuaiyun.com/weixin_64061088/article/details/128668010)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangyan_1010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值