Java中的Comparable接口和Comparator接口核心机制详解

 更新时间:2025年11月14日 09:47:26   作者:ty正在添砖Java  
Java中的Comparable和Comparator接口是实现对象比较和排序的两种核心机制,本文给大家介绍Java中的Comparable接口和Comparator接口区别,感兴趣的朋友一起看看吧

 Java 中的ComparableComparator接口是实现对象比较和排序的两种核心机制。

一个生动的比喻

想象一下排序一班的学生:

  • 使用 Comparable(自然排序):
    • 就像是规定:我们班默认的、永久的排队规则就是按学号排。每个学生(对象)的学号(自然属性)是固定的。老师只需要喊一声“按默认规则排队!”,学生A(this)就知道自己应该站在学生B(other)的前面还是后面,因为他们都清楚自己的学号。
    • 结论:你只能有一种默认规则。
  • 使用Comparator(定制排序):
    • 就像是老师今天说:“今天我们不按学号了,我们来按身高排”,于是老师(Comparator)拿着尺子(compare方法)来比较学生A(o1)和学生B(o2)的身高,然后决定他们的顺序。明天老师可以说:“今天按上次考试成绩排”,又拿出一个成绩单比较器。
    • 结论:你可以有无数种临时规则。

一、Comparable接口(内部比较器)

当一个类实现了 Comparable接口,就表明它的实例具有一种天生的、默认的比较顺序。例如,StringIntegerDate等类都实现了 Comparable,所以我们可以直接对它们的列表进行排序。 

核心:重写接口中唯一的 compareTo(T o)方法。

规则:this(当前对象)与参数对象 o比较。

返回一个负整数、零或正整数,分别表示 this小于、等于或大于 o。

下面我们举一个例子分别实现对Student的年龄和姓名进行比较:

年龄比较:

import java.util.Arrays;
//类实现接口,使其具备比较的功能
class Student implements Comparable<Student>{
    private String name;
    private int age;
    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }
    public int getAge() {
        return age;
    }
    public String getName(){
        return name;
    }
    //年龄排序
    public int compareTo(Student o){
//        return this.age - o.age;
        return Integer.compare(this.age,o.age);
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test01 {
    public static void main(String[] args) {
        //按照年龄从小到大排序
        Student[] students = new Student[]{
                new Student("xiaoming", 19),
                new Student("xiaohong",20),
                new Student("xiaohua",15),
                new Student("xiaoshuai",24),
        };
        System.out.println("排序前"+ Arrays.toString(students));
        //Arrays.sort()能够根据我们定义的 compareTo方法排序
        Arrays.sort(students);  
        System.out.println("排序后" + Arrays.toString(students));
            //两个对象进行比较
//        Student student1 = new Student("小明",28);
//        Student student2 = new Student("小红",19);
//        System.out.println(student1.compareTo(student2));    //输出9
//        <0 则s1<s2 =0则s1=s2 >0则s1>s2
}

修改一下Student类中的compareTo方法可以实现对姓名进行排序

//姓名排序
   public int compareTo(Student o){
       return this.name.compareTo(o.name);//在String中已经实现了compareTo方法,这里可直接调用
   }

根据ASCII值进行排序

为什么当我们的类实现了comparable接口后,我们能够直接通过Arrays.sort()方法对students数组按年龄或者姓名进行排序?

这是因为Arrays.sort()依赖于我们通过Comparable接口提供的比较规则。排序算法本身不知道如何比较两个Student对象,但是它知道可以调用我们实现的compareTo方法来获得比较结果,从而完成排序。

二、Comparator接口(外部比较器)

Comparator允许在不修改原类的情况下定义多种排序规则,是一种独立的比较器,更加灵活(定制排序)。

如何使用?

1.创建一个类实现Comparator<T>接口

2.重写接口中的 compare(T o1, T o2)方法。

  • 规则:比较两个参数对象 o1和 o2。
  • 返回一个负整数、零或正整数,分别表示 o1小于、等于或大于 o2。

下面我们举一个例子分别实现对Student的年龄和姓名进行比较:

按年龄以及姓名长度比较:

import java.util.Arrays;
import java.util.Comparator;
class Teacher{
    private String name;
    private int age;
    public Teacher(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Test02 {
    public static void main(String[] args) {
        Teacher[] teachers = new Teacher[]{
                new Teacher("Zhang",33),
                new Teacher("Li",40),
                new Teacher("Hen",28),
                new Teacher("Tang",18)
        };
        //按照年龄大小比较
        AgeComparator ageComparator = new AgeComparator();
        System.out.println("排序前" + Arrays.toString(teachers));
        Arrays.sort(teachers,ageComparator);    //将实例化的比较器作为参数传入sort
        System.out.println("按年龄排序后" + Arrays.toString(teachers));
        //按照姓名长度比较
        NameLenComparator nameLenComparator = new NameLenComparator();
        Arrays.sort(teachers,nameLenComparator);    //将实例化的比较器作为参数传入sort
        System.out.println("按姓名长度排序后" + Arrays.toString(teachers));
    }
    //(静态内部类)自定义年龄比较器
    static class AgeComparator implements Comparator<Teacher>{
        public int compare(Teacher o1,Teacher o2){
            return o1.getAge()- o2.getAge();
        }
    }
    //(静态内部类)自定义姓名长度比较器
    static class NameLenComparator implements Comparator<Teacher>{
        public int compare(Teacher o1,Teacher o2){
            return o1.getName().length() - o2.getName().length();
        }
    }
}

我们发现,当我们实现了Comparator接口,使用Arrays.sort()时,将我们自定义的比较器实例化的对象作为参数传入sort中实现了自定义排序,这使我们能够自定义多种比较器且不改变原有类进行排序,非常灵活。

Java8+引入的Lambda表达式

Java8+中我们可以通过Lambda表达式来简化我们的比较器代码:

// 使用Lambda表达式替代完整的比较器类
        // 按年龄排序
        Arrays.sort(teachers, (t1, t2) -> t1.getAge() - t2.getAge());
        System.out.println("按年龄排序后: " + Arrays.toString(teachers));
        // 按姓名长度排序
        Arrays.sort(teachers, (t1, t2) -> t1.getName().length() - t2.getName().length());
        System.out.println("按姓名长度排序后: " + Arrays.toString(teachers));
        // 甚至可以更复杂:先按年龄,年龄相同按姓名长度
        Arrays.sort(teachers, (t1, t2) -> {
            int ageCompare = t1.getAge() - t2.getAge();
            if (ageCompare != 0) {
                return ageCompare;
            }
            return t1.getName().length() - t2.getName().length();
        });
        System.out.println("按年龄和姓名长度排序后: " + Arrays.toString(teachers));
    }

下面我们来详解一下这串代码中Lambda表达式的用法:

// 按年龄排序
Arrays.sort(teachers, (t1, t2) -> t1.getAge() - t2.getAge());

分解说明:

  • (t1, t2):Lambda的参数列表,对应Comparator接口的compare方法的两个参数
  • ->:Lambda操作符,分隔参数和实现体
  • t1.getAge() - t2.getAge():Lambda的实现体,只有一行表达式,自动返回结果

带代码块的Lambda表达式

// 先按年龄,年龄相同按姓名长度
Arrays.sort(teachers, (t1, t2) -> {
    int ageCompare = t1.getAge() - t2.getAge();
    if (ageCompare != 0) {
        return ageCompare;
    }
    return t1.getName().length() - t2.getName().length();
});

分解说明:

  • 当实现逻辑需要多行代码时,使用{}包裹代码块
  • 代码块中需要显式使用return语句返回值
  • 这种多行Lambda适合处理复杂的比较逻辑

三、如何选择两个接口

Comparable接口和Comparator接口关键区别对比:

使用Comparable的情况

✅ 对象有明确的、唯一的自然排序规则

✅ 排序规则是对象固有的、不会改变的特性

✅ 你能够修改类的源代码

✅ 该排序规则会被频繁使用

示例:String, Integer, Date, BigDecimal

使用Comparator的情况

✅ 需要多种不同的排序规则

✅ 不能或不想修改原类代码

✅ 排序规则是临时的或特定于某个业务场景

✅ 需要复杂的、组合的排序逻辑

示例:报表排序、UI表格列排序、特殊业务规则排序

到此这篇关于Java中的Comparable接口和Comparator接口的文章就介绍到这了,更多相关java comparable接口和comparator接口内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • Mybatis查询时的延迟加载解析

    Mybatis查询时的延迟加载解析

    这篇文章主要介绍了Mybatis查询时的延迟加载解析,先从单表查询,需要时再从关联表去关联查询,能大大提高数据库性能,因为查询单表要比关联查询多张表速度要快,延迟加载分为两种:深度延时加载,侵入式延迟加载,需要的朋友可以参考下
    2023-10-10
  • mybatis报错 resultMapException的解决

    mybatis报错 resultMapException的解决

    这篇文章主要介绍了mybatis报错 resultMapException的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • 深入解析Java中的编码转换以及编码和解码操作

    深入解析Java中的编码转换以及编码和解码操作

    这篇文章主要介绍了Java中的编码转换以及编码和解码操作,文中详细解读了编码解码的相关IO操作以及内存使用方面的知识,需要的朋友可以参考下
    2016-02-02
  • Servlet中文乱码问题解决方案解析

    Servlet中文乱码问题解决方案解析

    这篇文章主要介绍了Servlet中文乱码问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Java程序打印奥林匹克标志方法详解

    Java程序打印奥林匹克标志方法详解

    这篇文章主要介绍了Java程序打印奥林匹克标志方法详解,需要的朋友可以参考下。
    2017-09-09
  • SpringBoot参数校验及原理全面解析

    SpringBoot参数校验及原理全面解析

    文章介绍了SpringBoot中使用@Validated和@Valid注解进行参数校验的方法,包括基本用法和进阶用法,如自定义验证注解、多属性联合校验和嵌套校验,并简要介绍了实现原理
    2024-11-11
  • RestTemplate接口调用神器常见用法汇总

    RestTemplate接口调用神器常见用法汇总

    这篇文章主要介绍了RestTemplate接口调用神器常见用法汇总,通过案例代码详细介绍RestTemplate接口调用神器常见用法,需要的朋友可以参考下
    2022-07-07
  • SpringMVC 重新定向redirect请求中携带数据方式

    SpringMVC 重新定向redirect请求中携带数据方式

    这篇文章主要介绍了SpringMVC 重新定向redirect请求中携带数据方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • IntelliJ IDEA 老司机居然还没用过 Stream Trace功能(问题小结)

    IntelliJ IDEA 老司机居然还没用过 Stream Trace功能(问题小结)

    很多朋友酷爱Java8 Stream功能,但是在使用过程中总不是那么顺利,下面通过本文给大家分享idea Stream Trace调试过程遇到的问题,需要的朋友参考下吧
    2021-05-05
  • Spring MVC 自定义数据转换器的思路案例详解

    Spring MVC 自定义数据转换器的思路案例详解

    本文通过两个案例来介绍下Spring MVC 自定义数据转换器的相关知识,每种方法通过实例图文相结合给大家介绍的非常详细,需要的朋友可以参考下
    2021-09-09

最新评论