深度解析Java中的Comparable接口和Comparator接口

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 深度解析Java中的Comparable接口和Comparator接口

新的一天,大家一起加油!

965dd4a559fd438091fed2834b4a99c8.gif


引子

我们之前的文章的文章提到了Arrays是一个数组工具类,用Arrays.sort能够对各种类型的数组进行排序,那下面的数组能不能用Arrays.sort排序呢?

class Student {  // 自定义的学生类
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override  // 对父类Object的父类方法进行重写,以便直接打印出当前对象的值
    public String toString() { 
        // 这是IDEA的自动生成的toString重写,你也可以按照自己的喜好自由的选择打印的形式
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class test4 {
    public static void main(String[] args) {
        Student[] students = new Student[] { // 我们定义了一个数组,数组中每个元素都是一个学生对象
              new Student("zhangsan", 13),
              new Student("lisi", 23),
              new Student("able", 17),
        };
        Arrays.sort(students); // 用类Arrays.sort对students数组进行排序
        System.out.println(Arrays.toString(students));
        // 注意这里的toString是Arrays自己的toString方法,和Object类中的toString方法不是同一个方法,
        // 只是碰巧同名,参数都不一样,Arrays的有参数,Object没参数
        // 我们前几篇文章专门对Arrays以及toString方法的重写做了说明,这里不再赘述
    }
}

但你运行的时候,你会发现程序报错了😮

对了,我记得sort只能对给定长度的数组排序,否则会发出空指针异常,但这里就是给定长度的数组啊!为啥还是错呢?

bbb0bbbce2944cdb8d0e02803c0a82a1.png

程序抛出了异常😮,你说不对呀!我的Arrays不是对任意的数组都可以进行操作吗?之前我们就用Arrays.toString成功打印了数组元素都是对象的一个数组呀!

但是朋友,你告诉Arrays拿什么排了吗?是按姓名 还是按年龄呢? 

🍑所以我们要告诉编译器那什么来排序,而这就要用到我们的Comparable接口


Comparable接口

📝通过参考手册我们可以看到

c3a00f457ef548c6970eb4ce81bf9b5c.png

我们的程序就可以写成这样

//Comparable接口在java.lang这个包下面,而java.lang这个包由java解释器自动引入,而不用import语句引入
class Student implements Comparable<Student>{  // 实现Comparable接口,注意Comparable后要加上要比较的对象的类型<Student>
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override  // 对父类Object的父类方法进行重写,以便直接打印出当前对象的值
    public String toString() {
        return "[" + this.name + ":" + this.age + "]";
    }
    @Override // 实现Comparable接口中的中的compareTo方法
    public int compareTo(Student o) {
        if (this.age > o.age) return 1;  // this.代表对当前对象的引用,o.代表对参数对的引用
        else if (this.age < o.age) return -1;
        else return 0;
    }
    //如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    //比如上面我们如果this.age>o.age,那么返回1,就this.age在o.age的后面,即按年龄升序排列
    //如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    //如果当前对象和参数对象不分先后, 返回 0;
}
public class test4 {
    public static void main(String[] args) {
        Student[] students = new Student[] { // 我们定义了一个数组,数组中每个元素都是一个学生对象
              new Student("zhangsan", 13),
              new Student("lisi", 23),
              new Student("able", 17),
        };
        Arrays.sort(students); // 用类Arrays.sort对students数组进行排序
        System.out.println(Arrays.toString(students));
        // 注意这里的toString是Arrays自己的toString方法,和Object类中的toString方法不是同一个方法,
        // 只是碰巧同名,参数都不一样,Arrays的有参数,Object没参数
        // 我们前几篇文章专门对Arrays以及toString方法的重写做了说明,这里不再赘述
    }
}

😁看一下输出结果

fc645447b4ae4161a7149ef0feb3e4a6.png

📝咱们再回过头来看看为为啥必须要实现接口中的 compareTo 方法,

f17181848d5d4f7c8db59342ed1d007e.png

上图中的这个(Comparable)a[runHi++]数组就是我们Arrays.sort传过去的学生对象数组🤔,下图中的this指的是谁🤔?就要看谁此时调用了compareTo方法😎


上图中compareTo(a[lo])点号前面的的学生对象a[runHi++]就是此时的this😮。下图中的o对象就是上图中的compareTo(a[lo]中的a[lo]学生对象。

0e1efe6a89ac45578efcb3e47ff3ab19.png

不知道你理解了吗😂?如果没理解,我们不妨再举一个栗子

2d1da0b6423644359a6fd73c8df22725.png


🌰总结一下


📝我们知道在Arrays.sort(students); 中是传了一个学生对象数组,如上图所示在调用Arrays对对象数组排序的时候,其实就调用了我们的Comparable接口中的compareTo方法对数组的每个元素进行了排序😁


📝其实在Java中对于引用数据类型的比较或者排序,一般都要用到使用Comparable接口中的compareTo() 方法


🍑那如何按年龄排序呢?一样的道理(只是发生了一些小小的变化)

//Comparable接口在java.lang这个包下面,而java.lang这个包由java解释器自动引入,而不用import语句引入
class Student implements Comparable<Student>{  // 实现Comparable接口,注意Comparable后要加上要比较的对象的类型<Student>
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override  // 对父类Object的父类方法进行重写,以便直接打印出当前对象的值
    public String toString() {
        return "[" + this.name + ":" + this.age + "]";
    }
    @Override // 实现Comparable接口中的中的compareTo方法
    public int compareTo(Student o) {
        // 为啥在compareTo里还要用compareTo,因为name是个String类型也是个引用类型,也需要用compareTo比较, 不过这里用到的compareTo方法是String类里重写的
        if (this.name.compareTo(o.name) > 0) return 1;  // this.代表对当前对象的引用,o.代表对参数对的引用
        else if (this.name.compareTo(o.name) < 0) return -1;
        else return 0;
    }
    //如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    //如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    //如果当前对象和参数对象不分先后, 返回 0;
}
public class test4 {
    public static void main(String[] args) {
        Student[] students = new Student[] { // 我们定义了一个数组,数组中每个元素都是一个学生对象
              new Student("zhangsan", 13),
              new Student("lisi", 23),
              new Student("able", 17),
        };
        Arrays.sort(students); // 用类Arrays.sort对students数组进行排序
        System.out.println(Arrays.toString(students));
        // 我们前几篇文章专门对Arrays以及toString方法的重写做了说明,这里不再赘述
    }
}

046012e86ae643f39b030a891fc03180.png

📝但这样会有一个问题,那就是一但写好了排序的方法,那就一下子就写死了😮。你在compareTo方法里实现的是按年龄比较,那你接下来就只能按年龄来比较🤔。如果想按姓名来比较,那只能够在重写compareTo方法。


🍑那有什么办法能够解决这个问题呢?

有!那就是我们接下来要讲的比较器:Comparator接口


3bfee300c96b4c11a26de0787bc871c4.png所以就像Comparable 接口一样,我们只要实现了Comparator接口,并重写Comparator里的compare方法就可以实现对学生对象数组的排序


🌰比如我们上面的年龄比较就可以写成这样

1.class Student { 
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override  // 对父类Object的父类方法进行重写,以便直接打印出当前对象的值
    public String toString() {
        return "[" + this.name + ":" + this.age + "]";
    }
}
class AgeComparator implements Comparator<Student> { // 年龄比较器
    @Override  // 实现Comparator接口中的compare方法
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    // 反正返回的也是数字,当o1.age>o2.age时返回大于零的数,即o1对象排在o2对象的后面,升序排列,我们之前用Comparable接口时也可以这样简写
    }
    //如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    //如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    //如果当前对象和参数对象不分先后, 返回 0;
}
public class test4 {
    public static void main(String[] args) {
        Student[] students = new Student[]{ // 我们定义了一个数组,数组中每个元素都是一个学生对象
                new Student("zhangsan", 13),
                new Student("lisi", 23),
                new Student("able", 17),
        };
        AgeComparator ageComparator = new AgeComparator(); // 对年龄比较器进行实例化
        Arrays.sort(students, ageComparator); 
        // 用类Arrays.sort对students数组进行排序,这里传了两个参数(学生对象和所对应的年龄比较器)
        System.out.println(Arrays.toString(students));
        // 我们前几篇文章专门对Arrays以及toString方法的重写做了说明,这里不再赘述
    }
}


56e618d467254cf5ab6745b6b578d220.png

📝Arrays.sort此时是一个传两个参数的方法:

275844db488a41b681e5c7a9dd12b584.png

🌰那姓名比较比较自然就出来了

class Student {
    String name;
    int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override  // 对父类Object的父类方法进行重写,以便直接打印出当前对象的值
    public String toString() {
        return "[" + this.name + ":" + this.age + "]";
    }
}
class NameComparator implements Comparator<Student> { // 姓名比较器
    @Override  // 实现Comparator接口中的compare方法
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name); 
// 因为name是String类型,也是一个引用类型,也要用到compareTo方法,此时的compareTo方法是String类里重写的方法
    }
    //如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    //如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    //如果当前对象和参数对象不分先后, 返回 0;
}
public class test4 {
    public static void main(String[] args) {
        Student[] students = new Student[]{ // 我们定义了一个数组,数组中每个元素都是一个学生对象
                new Student("zhangsan", 13),
                new Student("lisi", 23),
                new Student("able", 17),
        };
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students, nameComparator); // 用类Arrays.sort对students数组进行排序
        System.out.println(Arrays.toString(students));
        // 我们前几篇文章专门对Arrays以及toString方法的重写做了说明,这里不再赘述
    }
}

总结一下就是:

📝Comparable接口和Comparator接口都是Java中用来比较和排序引用类型数据的接口,要实现比较,就需要实现他们所各种对应的compareTo方法或者compare方法。


🌰不同的是:Comparator使用起来更加灵活,所以我更倾向于使用比较器:Comparator


但注意,sort只能对给定长度的数组排序,否则会发出空指针异常

好了,今天我们就聊到这里,咱们下篇博客见🥰

7f47fc754a8249469e906c488d997461.jpg

相关文章
|
16天前
|
Java 编译器
Java 泛型详细解析
本文将带你详细解析 Java 泛型,了解泛型的原理、常见的使用方法以及泛型的局限性,让你对泛型有更深入的了解。
29 2
Java 泛型详细解析
|
2天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
25 6
|
17天前
|
缓存 监控 Java
Java线程池提交任务流程底层源码与源码解析
【11月更文挑战第30天】嘿,各位技术爱好者们,今天咱们来聊聊Java线程池提交任务的底层源码与源码解析。作为一个资深的Java开发者,我相信你一定对线程池并不陌生。线程池作为并发编程中的一大利器,其重要性不言而喻。今天,我将以对话的方式,带你一步步深入线程池的奥秘,从概述到功能点,再到背景和业务点,最后到底层原理和示例,让你对线程池有一个全新的认识。
47 12
|
14天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
14天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
16天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
19天前
|
存储 缓存 监控
Java中的线程池深度解析####
本文深入探讨了Java并发编程中的核心组件——线程池,从其基本概念、工作原理、核心参数解析到应用场景与最佳实践,全方位剖析了线程池在提升应用性能、资源管理和任务调度方面的重要作用。通过实例演示和性能对比,揭示合理配置线程池对于构建高效Java应用的关键意义。 ####
|
29天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
67 2
|
2月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
75 0
|
2月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
57 0

推荐镜像

更多