Comparable接口和Comparator接口

举报
VIBE 发表于 2022/10/20 16:00:31 2022/10/20
【摘要】 前言博主在很早之前写过一篇面向对象编程的文章,其中粗略的介绍了一下Comparable接口的使用,现在问题来了,Comparabe接口和Comparator接口的异同点是什么呢? 一、元素的比较 1.1 基本类型的比较在Java中,基本类型的对象可以直接比较大小。 1.2 对象的比较Java中引用类型的变量不能直接按照 > 或者 < 方式进行比较。那为什么==可以比较?因为:对于用户实现自...

前言

博主在很早之前写过一篇面向对象编程的文章,其中粗略的介绍了一下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中的对象按照某种规则去排序,不想用的时候可以换另一种排序类。(比如升序和降序)
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。