• Comparable接口和Comparator接口


    CSDN话题挑战赛第2期
    参赛话题:学习笔记

    前言

    博主在很早之前写过一篇面向对象编程的文章,其中粗略的介绍了一下Comparable接口的使用,现在问题来了,Comparabe接口和Comparator接口的异同点是什么呢?

    一、元素的比较

    1.1 基本类型的比较

    在Java中,基本类型的对象可以直接比较大小

    1.2 对象的比较

    Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。

    那为什么==可以比较?

    因为:对于用户实现自定义类型,都默认继承自Object类,而Object类中提供了equals方法,而==默认情况下调用的就是equals方法,但是该方法的比较规则是:没有比较引用变量引用对象的内容,而是直接比较引用变量的地址,但有些情况下该种比较就不符合题意。

    //Object中equal的实现,可以看到:直接比较的是两个引用变量的地址
    public boolean equals(Object obj) {
    return (this == obj);
    }
    

    二、引用对象的比较

    有些情况下,需要比较的是对象中的内容,比如:向优先级队列中插入某个对象时,需要对按照对象中内容来调整堆,那该如何处理呢?

    2.1 覆写基类的equals

    public class Card {
        public int rank; // 数值
        public String suit; // 花色
    
        public Card(int rank, String suit) {
            this.rank = rank;
            this.suit = suit;
        }
    
        @Override
        public boolean equals(Object o) {
            // 自己和自己比较
            if (this == o) {
                return true;
            }
            // o如果是null对象,或者o不是Card的子类
            if (o == null || !(o instanceof Card)) {
                return false;
            }
            // 注意基本类型可以直接比较,但引用类型最好调用其equal方法
            Card c = (Card) o;
            return rank == c.rank && suit.equals(c.suit);
        }
    }
    

    注意: 一般覆写 equals 的套路就是上面演示的

    1. 如果指向同一个对象,返回 true
    2. 如果传入的为 null,返回 false
    3. 如果传入的对象类型不是 Card,返回 false
    4. 按照类的实现目标完成比较,例如这里只要花色和数值一样,就认为是相同的牌
    5. 注意下调用其他引用类型的比较也需要 equals,例如这里的 suit 的比较
    6. 覆写基类equal的方式虽然可以比较,但缺陷是:equal只能按照相等进行比较,不能按照大于、小于的方式进行比较。

    2.2 Comparable接口

    接下来将用一个例子介绍java.lang.Comparable接口:

    给对象数组排序

    给定一个学生类

    class Student {
        private String name;
        private int score;
    
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "[" + this.name + ":" + this.score + "]";
        }
    }
    

    再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序).

    Student[] students = new Student[] {
                new Student("张三", 95),
                new Student("李四", 96),
                new Student("王五", 97),
                new Student("赵六", 92),
        }
    

    按照我们之前的理解, 数组工具类我们有一个现成的 sort 方法, 能否直接使用这个方法呢?

    Arrays.sort(students);
    System.out.println(Arrays.toString(students));
     
     // 运行出错, 抛出异常. 
    Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to 
    java.lang.Comparable    
    

    仔细思考, 不难发现, 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系怎么确定? 需要我们额外指定

    让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法:

    class Student implements Comparable {
        private String name;
        private int score;
        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
        @Override
        public String toString() {
            return "[" + this.name + ":" + this.score + "]";
        }
        @Override
        public int compareTo(Object o) {
           if(this == 0){
           return 0; //返回0表示相等
           }
           if(o instanceof Student){
           //当前传入的o就是Student类型的引用,向下转型还原为Student
           //要比较Student对象的大小关系,就要用到Student的独有属性,向下转型
           Student stu = (Student)o;
           return this.score - stu.score;
           }
           //若传入不是学生类,则抛出异常
           throw new IllegalArgumentException("不是学生类型,无法比较!")
    } 
    

    在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.

    然后比较当前对象和参数对象的大小关系(按分数来算).

    • 如果当前对象应排在参数对象之前, 返回小于 0 的数字;

    • 如果当前对象应排在参数对象之后, 返回大于 0 的数字;

    • 如果当前对象和参数对象不分先后, 返回 0;

    再次执行程序, 结果就符合预期了

    // 执行结果
    [[王五:97], [李四:96], [张三:95], [赵六:92]] 
    

    注意事项:
    对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则。

    为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序):(其实Arrays.sort()内部也是和下面代码类似的,只是被封装了)

    public static void sort(Comparable[] array) {
            for (int bound = 0; bound < array.length; bound++) {
                for (int cur = array.length - 1; cur > bound; cur--) {
                    if (array[cur - 1].compareTo(array[cur]) > 0) {
                        // 说明顺序不符合要求, 交换两个变量的位置
                        Comparable tmp = array[cur - 1];
                        array[cur - 1] = array[cur];
                        array[cur] = tmp;
                    }
                }
            }
        }
    

    再次执行代码

    sort(students); 
    System.out.println(Arrays.toString(students)); 
    // 执行结果
    [[王五:97], [李四:96], [张三:95], [赵六:92]] 
    

    2.3 Comparator接口

    用户自定义比较器类,实现Comparator接口:

    Public interface Comparator<T> {
    // 返回值:
    // < 0: 表示 o1 指向的对象小于 o2 指向的对象
    // == 0: 表示 o1 指向的对象等于 o2 指向的对象
    // > 0: 表示 o1 指向的对象等于 o2 指向的对象
    int compare(T o1, T o2);
    }
    
    

    假设现在覆写了Comparable接口的compareTo方法:
    在这里插入图片描述

    • 如果需要得到一个升序数组 => this - o
    • 要得到一个降序数组,又得改代码 => o-this

    假设如今业务需要要更改成逆序的,又得更改CompareTo方法。

    而程序开发中遵循一个开闭原则:

    一段程序应该对扩展开放,对修改关闭。
    写好的代码别老改,要有新场景,就拓展新代码,不要影响老代码。

    所以我们可以使用Comparator接口:

    一个类若实现了这个接口,表示这个类天生就是为别的类的大小关系服务的:
    在这里插入图片描述StudentSec这个类天生就是为了Student对象的排序而存在

    覆写compare方法(o1,o2) => int

    表示o1和o2的大小关系:
    返回值 >0 :o1> o2
    返回值 =0 :o1 = o2
    返回值<0 :o < o2

    当把Student类的大小关系比较从Student类中“解耦”,此时的比较策略非常灵活,需要哪种方式,只需要创新一个新的类实现Comparator接口即可,根据此时大小关系的需要传入比较器的对象。(策略模式)

    在这里插入图片描述

    三、Comparable和Comparator的异同

    3.1 共同点

    1. 这两个接口一般都是用来实现集合内的排序(如Collections.sort() 、Arrays.sort()、List.sort(Comparator))。

    以下是Collections类下的sort方法,可以传入继承Comparable接口的泛型对象,也可以传入当前list 和 一个comparator比较器。

    在这里插入图片描述在这里插入图片描述

    1. 当调用集合排序方法的时候,如果直接传入list就会自行调用对象的compareTo()方法来实现对象的比较。如果传入list和coparator的话,就会调用compare()方法

    3.2 区别

    1. Comparable接口位于java.lang包下,Comparator位于java.util包下
    2. Comparable接口只提供了一个compareTo()方法;Comparator接口不仅提供了compare()方法【两个参数】,还提供了其他默认方法,如reversed()、thenComparing(),使我们可以按照更多的方式进行排序
    3. Comparator相对于Comparable来说更加的灵活,耦合度低
    4. 如果要用Comparable接口,则必须实现这个接口,并重写compareTo()方法【一个参数】;但是Comparator接口可以在类外部使用,通过将该接口的一个匿名类对象当做参数传递给Collections.sort()方法或者Arrays.sort()方法实现排序。
    5. Comparator体现了一种策略模式,即可以不用要把比较方法嵌入到类中,而是可以单独在类外部使用,这样我们就可有不用改变类本身的代码而实现对类对象进行排序。

    总结

    用简单的话来讲就是:

    • Comparable接口可以让当前这个类拥有可以比较的能力,就可以和类型的对象进行比较
    • Comparator接口可以让我们定义不同的类,然后我们可以用这些自己定义好的排序类去对list中的对象按照某种规则去排序,不想用的时候可以换另一种排序类。(比如升序和降序)
  • 相关阅读:
    CBC算法实践Demo
    华为eNSP配置专题-OSPF路由协议的配置
    JUC锁: ReentrantReadWriteLock详解
    Word的”交叉引用“和”插入题注“快捷键设置
    Android音视频开发:MediaCodec解码视频,得到YUV值,一帧一帧加载到SD卡中保存
    MATLAB运算符
    【flask入门系列】Flask-SQLAlchemy的使用
    ds项目管理
    国内主机整车EEA架构汇总
    HarmonyOS鸿蒙原生应用开发设计- 图标库
  • 原文地址:https://blog.csdn.net/qq_43575801/article/details/127113875