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

简介: 深度解析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

相关文章
|
7月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
318 1
|
8月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
245 11
|
9月前
|
存储 缓存 安全
Java集合框架(二):Set接口与哈希表原理
本文深入解析Java中Set集合的工作原理及其实现机制,涵盖HashSet、LinkedHashSet和TreeSet三大实现类。从Set接口的特性出发,对比List理解去重机制,并详解哈希表原理、hashCode与equals方法的作用。进一步剖析HashSet的底层HashMap实现、LinkedHashSet的双向链表维护顺序特性,以及TreeSet基于红黑树的排序功能。文章还包含性能对比、自定义对象去重、集合运算实战和线程安全方案,帮助读者全面掌握Set的应用与选择策略。
1001 23
|
9月前
|
存储 安全 Java
Java集合框架(一):List接口及其实现类剖析
本文深入解析Java中List集合的实现原理,涵盖ArrayList的动态数组机制、LinkedList的链表结构、Vector与Stack的线程安全性及其不推荐使用的原因,对比了不同实现的性能与适用场景,帮助开发者根据实际需求选择合适的List实现。
|
7月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
370 1
|
7月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
348 1
|
8月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
341 0
|
8月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
516 16
|
9月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。

推荐镜像

更多
  • DNS