Comparable接口和Comparator接口 梦里梦外; 2023-09-26 09:23 3阅读 0赞 [CSDN话题挑战赛第2期][CSDN_2] 参赛话题:[学习笔记][Link 1] ## 前言 ## 博主在很早之前写过一篇面向对象编程的文章,其中粗略的介绍了一下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方法: ![在这里插入图片描述][a4aaac016e7b4971b6fff076876c1a1b.png] * 如果需要得到一个升序数组 => this - o * 要得到一个降序数组,又得改代码 => o-this 假设如今业务需要要更改成逆序的,又得更改CompareTo方法。 而程序开发中遵循一个**开闭原则:** **一段程序应该对扩展开放,对修改关闭。 写好的代码别老改,要有新场景,就拓展新代码,不要影响老代码。** 所以我们可以使用Comparator接口: **一个类若实现了这个接口,表示这个类天生就是为别的类的大小关系服务的:** ![在这里插入图片描述][aab9038734284865bac1ae2ad4a6f072.png]StudentSec这个类天生就是为了Student对象的排序而存在 覆写compare方法(o1,o2) => int **表示o1和o2的大小关系: 返回值 >0 :o1> o2 返回值 =0 :o1 = o2 返回值<0 :o < o2** **当把Student类的大小关系比较从Student类中“解耦”,此时的比较策略非常灵活,需要哪种方式,只需要创新一个新的类实现Comparator接口即可,根据此时大小关系的需要传入比较器的对象。(策略模式)** ![在这里插入图片描述][85f50c60c7dd4ddfaf44911ea8ab1512.png] ## 三、Comparable和Comparator的异同 ## #### 3.1 共同点 #### 1. 这两个接口一般都是用来实现集合内的排序(如Collections.sort() 、Arrays.sort()、List.sort(Comparator))。 **以下是Collections类下的sort方法,可以传入继承Comparable接口的泛型对象,也可以传入当前list 和 一个comparator比较器。** ![在这里插入图片描述][edbe96cf1bdf4ef9b7ef5ed6d1826127.png]![在这里插入图片描述][3a98493722f24ec68ff8542a4eedffa2.png] 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中的对象按照某种规则去排序,不想用的时候可以换另一种排序类。(比如升序和降序) [CSDN_2]: https://marketing.csdn.net/p/7b6697fd9dd3795a268d1a6f2fe75012 [Link 1]: https://activity.csdn.net/creatActivity?id=10213 [a4aaac016e7b4971b6fff076876c1a1b.png]: https://img-blog.csdnimg.cn/a4aaac016e7b4971b6fff076876c1a1b.png [aab9038734284865bac1ae2ad4a6f072.png]: https://img-blog.csdnimg.cn/aab9038734284865bac1ae2ad4a6f072.png [85f50c60c7dd4ddfaf44911ea8ab1512.png]: https://img-blog.csdnimg.cn/85f50c60c7dd4ddfaf44911ea8ab1512.png [edbe96cf1bdf4ef9b7ef5ed6d1826127.png]: https://img-blog.csdnimg.cn/edbe96cf1bdf4ef9b7ef5ed6d1826127.png [3a98493722f24ec68ff8542a4eedffa2.png]: https://img-blog.csdnimg.cn/3a98493722f24ec68ff8542a4eedffa2.png
还没有评论,来说两句吧...