当前位置: 首页 > news >正文

Java 函数接口Comparator和Comparable【比较器接口】详解与示例

通常要对数据进行排序,数据对象的类需要实现 Comparable 接口。有的情形需要在不修改类本身的情况下定义多种排序规则,则可以使用 Comparator 接口。所以这两种接口均用于排序,但使用方式略有不同。Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

一、Comparable 接口简介

Comparable 是可排序接口,它是一个泛型接口,可以指定比较的对象类型。
Comparable 接口定义如下:

	package java.lang;public interface Comparable<T> {public int compareTo(T o);}

接口定义中的T表示数据对象的类型,这里<T>表示泛型类型。通常T都是实现该接口的类本身。
函数接口只能有一个抽象方法,Comparable 接口的唯一抽象方法是比较器:compareTo(T o)。
如果一个类实现了Comparable接口,则需要实现compareTo方法。

比较规则:
假设我们通过“a.compareTo(b)”来比较a和b的大小。对象a与对象b比较,如果a小于b,则返回负整数;如果相等则返回0;如果a大于b,则返回正整数。

示例一:Student实现Comparable接口的完整示例

package function;
public class Student implements Comparable<Student> {private String name;private int age;private int score;public Student(String name, int age,int score) {this.name = name;this.age = age;this.score = score;}public int getScore() {return score;}@Overridepublic int compareTo(Student s) {/***根据年龄比较***/return this.age-s.age;}public static void main(String[] args) {Student stu1=new Student("Alice",18,80);Student stu2=new Student("Bob",20, 68);System.out.println(stu1.compareTo(stu2));}
}

根据不同属性可实现不同的比较器:

  • 示例中的比较器是根据年龄比较:
    @Overridepublic int compareTo(Student s) {/***根据年龄比较***/return this.age-s.age;}
  • 如果要根据成绩比较,则compareTo(Student s)方法要更新为如下所示:
    @Overridepublic int compareTo(Student s) {/***根据成绩比较***/return this.score-s.score;}
  • 如果要根据姓名比较,则compareTo(Student s)方法要更新为如下所示:
    @Overridepublic int compareTo(Student s) {/***根据年龄比较***///return this.age-s.age;/***根据成绩比较***///return this.score-s.score;/***根据姓名比较***/return this.name.compareTo(s.name);}

因为,姓名的类型是String类,String类内部已经实现了compareTo方法,所以我们可以直接调用compareTo方法来实现姓名的比较。

若一个类实现了Comparable接口,就意味着“该类支持排序”。 实现后,在排序时这个类则可以按照compareTo定义的规则进行排序,无需额外指定比较器。
对于实现Comparable接口的类的对象的List列表(或数组)”,我们就可以通过 Collections.sort(或 Arrays.sort)来对该List列表(或数组)进行排序。

示例二:Student实现Comparable接口,用列表演示的完整示例

package function;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class Student implements Comparable<Student> {private String name;private int age;private int score;public Student(String name, int age,int score) {this.name = name;this.age = age;this.score = score;}public int getScore() {return score;}@Overridepublic int compareTo(Student s) {/***根据年龄比较***///return this.age-s.age;/***根据成绩比较***///return this.score-s.score;/***根据姓名比较***/return this.name.compareTo(s.name);}@Overridepublic String toString() {return "Student {姓名:"+name+" 年龄:"+age +"  成绩:"+score+"}";}public static void main(String[] args) {Student stu1=new Student("Alice",18,80);Student stu2=new Student("Bob",20, 68);//System.out.println(stu1.compareTo(stu2));List<Student> list = new ArrayList<>();list.add(stu2);list.add(stu1);list.add(new Student("David",16, 85));list.add(new Student("Charlie",19, 76));System.out.println("排序前:");list.forEach(System.out::println);System.out.println("排序后:");Collections.sort(list);list.forEach(System.out::println);}
}

测试结果图:
在这里插入图片描述

示例三:一个用冒泡排序进行排序的例程

冒泡排序用到的Student类的定义:

public class Student implements Comparable<Student> {  private String name;private int age;private int score;public Student(String name, int age,int score) {this.name = name;this.age = age;this.score = score;}public int getAge() {return age;}public int getScore() {return score;}public String getName() {return name;}@Overridepublic int compareTo(Student s) {/***根据姓名比较***/return this.name.compareTo(s.name);}@Overridepublic String toString() {return "Student {姓名:"+name+" 年龄:"+age +"  成绩:"+score+"}";}
}

冒泡排序主程序:

public class BubbleSort {public static void sortA(Comparable comparables[]){for (int i = 0; i < comparables.length-1; i++) {for(int j=0;j<comparables.length-1-i;j++){if(comparables[j].compareTo(comparables[j+1])>0){Comparable tmp=comparables[j];comparables[j]=comparables[j+1];comparables[j+1]=tmp;}}}}public static void main(String[] args) {Student[] students=new Student[]{new Student("Bob",20, 68),new Student("Alice",18,80),new Student("David",16, 85),new Student("Charlie",19, 76)};System.out.println("排序前");for (int i = 0; i < students.length; i++) {System.out.println(students[i]);}sortA(students);System.out.println("排序后");for (int i = 0; i < students.length; i++) {System.out.println(students[i]);}}}

测试效果图:
在这里插入图片描述

示例四:一个实现Comparable接口的电话号码,排序完整示例:

package function;import java.util.Arrays;public class Telephone implements Comparable<Telephone> {private final int countryCode; //国家代码private final String areaCode; //区号private final int number; //电话号码public Telephone(int countryCode, String areaCode, int number) {this.countryCode = countryCode;this.areaCode = areaCode;this.number = number;}@Overridepublic int compareTo(Telephone o) {int result = Integer.compare(countryCode, o.countryCode);if (0 == result) {result = String.CASE_INSENSITIVE_ORDER.compare(areaCode, o.areaCode);if (0 == result) {result = Integer.compare(number, o.number);}}return result;}public static void main(String[] args) {Telephone[] telephones = new Telephone[]{new Telephone(86, "010", 86180412),new Telephone(86, "010", 56279866),new Telephone(86, "021", 68367160),new Telephone(86, "0574", 86979122),new Telephone(86, "0573", 68787826),new Telephone(86, "021", 42965686)};// 数组排序Arrays.sort(telephones);// 打印排序后的数组元素Arrays.stream(telephones).forEach(System.out::println);}@Overridepublic String toString() {return "PhoneNumber{" +"countryCode=" + countryCode +", areaCode=" + areaCode +", number=" + number +'}';}
}

测试效果图:
在这里插入图片描述

与排序相关的其他应用场景
当创建有序集合(如TreeSet)或有序映射(如TreeMap)时,如果类(例如Student类)未实现可排序接口,可指定排序比较器。例如,在创建树集TreeSet时可指定一个比较器定制排序。
下面的代码,假设Student类未实现可排序接口时,按分数排序时指定Lambda表达式作为排序比较器:

	TreeSet<Student> set =  new TreeSet<>( (a,b)->a.getScore()-b.getScore() );//λ表达式

当Student类实现了Comparable接口按分数排序时,则创建有序集合时就不需要指定排序器。下面的创建方式与上面是等价的。

	TreeSet<Student> set =  new TreeSet<>();

二、Comparator 接口简介

Comparator 是比较器接口,它是一个泛型接口,可以指定比较的对象类型。

我们若要对某个没有实现可排序接口(Comparable接口)的类进行排序;这时我们可以建立一个“该类的比较器”来进行排序。这个“比较器”只需要实现Comparator接口即可。

也就是说,我们可以通过“实现Comparator类来新建一个比较器”,然后通过该比较器对类进行排序。
实际上,Comparator接口的实现使用的是设计模式中的策略模式。

Comparator 接口的定义如下,有两个抽象方法:

package java.util;
public interface Comparator<T> {int compare(T a, T b) ;    //比较器boolean equals(Object obj);  //相等比较
}

说明: 函数接口只能有一个抽象方法。但是,Comparator接口却有二个抽象方法,其声明为函数接口,编译器也能通过其规范性检查,而且其实现类中不实现equals方法也没关系,是否令人疑惑不解呢?根据Java文档的解释,所有的类、接口都继承自超类Object类;如果接口中有抽象方法,是对超类Object类中public方法的重写,就不算真正的抽象方法。因此,Comparator接口中的抽象方法equals()是不算抽象方法的。

下面的示例是使用Lambda表达式来实现Comparator的比较器来排序:

		//集合类的整型数的列表List,排序List<Integer> numbers = Arrays.asList(6,12,4,9);numbers.sort((a,b)->Integer.compare(a,b));

比较规则:
比较规则Comparator与Comparable接口是一致的。
假设我们通过“(a,b)->Integer.compare(a,b)”来比较a和b的大小。对象a与对象b比较,如果a小于b,则返回负整数;如果相等则返回0;如果a大于b,则返回正整数。

示例五:如果Student未实现Comparable接口,我们可以定义多个外部比较器,完整示例:

package function;import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;public class Student { //implements Comparable<Student> {private String name;private int age;private int score;public Student(String name, int age,int score) {this.name = name;this.age = age;this.score = score;}public int getAge() {return age;}public int getScore() {return score;}public String getName() {return name;}/***@Overridepublic int compareTo(Student s) {/***根据年龄比较***///return this.age-s.age;/***根据成绩比较***///return this.score-s.score;/***根据姓名比较***return this.name.compareTo(s.name);}***/    @Overridepublic String toString() {return "Student {姓名:"+name+" 年龄:"+age +"  成绩:"+score+"}";}public static void main(String[] args) {Student stu1=new Student("Alice",18,80);Student stu2=new Student("Bob",20, 68);//System.out.println(stu1.compareTo(stu2));List<Student> list = new ArrayList<>();list.add(stu2);list.add(stu1);list.add(new Student("David",16, 85));list.add(new Student("Charlie",19, 76));System.out.println("排序前:");list.forEach(System.out::println);System.out.println("按姓名排序后:");Collections.sort(list, new NameComparator());list.forEach(System.out::println);System.out.println("按年龄排序后:");Collections.sort(list, new AgeComparator());list.forEach(System.out::println);System.out.println("按成绩排序后:");Collections.sort(list, new ScoreComparator());list.forEach(System.out::println);}
}class AgeComparator implements Comparator<Student> {@Overridepublic int compare(Student s1, Student s2) {return s1.getAge()- s2.getAge();}
}class ScoreComparator implements Comparator<Student> {@Overridepublic int compare(Student s1, Student s2) {return s1.getScore()-s2.getScore();}
}class NameComparator implements Comparator<Student> {@Overridepublic int compare(Student s1, Student s2) {return s1.getName().compareTo(s2.getName());}
}

在这里插入图片描述
Comparator接口的常用方法

Comparator接口除了一个compare()抽象方法外,Comparator接口还提供了一些默认方法和静态方法,用于方便地创建和组合不同的比较器。这些方法主要有:

  • reversed(): 返回一个与当前比较器相反顺序的比较器。
  • thenComparing(Comparator<? super T> other): 返回一个先按照当前比较器进行比较,如果相等则按照other比较器进行比较的复合比较器。
  • thenComparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator): 返回一个先按照当前比较器进行比较,如果相等则按照keyExtractor提取出来的键值按照keyComparator进行比较的复合比较器。
  • thenComparingInt(ToIntFunction<? super T> keyExtractor): 返回一个先按照当前比较器进行比较,如果相等则按照keyExtractor提取出来的int值进行比较的复合比较器。
  • thenComparingLong(ToLongFunction<? super T> keyExtractor): 返回一个先按照当前比较器进行比较,如果相等则按照keyExtractor提取出来的long值进行比较的复合比较器。
  • thenComparingDouble(ToDoubleFunction<? super T> keyExtractor): 返回一个先按照当前比较器进行比较,如果相等则按照keyExtractor提取出来的double值进行比较的复合比较器。
  • naturalOrder(): 返回一个按照自然顺序进行比较的比较器,要求被比较的对象实现了Comparable接口。
  • reverseOrder(): 返回一个按照自然顺序相反进行比较的比较器,要求被比较的对象实现了Comparable接口。
  • comparing(Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator): 返回一个按照keyExtractor提取出来的键值按照keyComparator进行比较的比较器。
  • comparing(Function<? super T, ? extends U> keyExtractor): 返回一个按照keyExtractor提取出来的键值按照自然顺序进行比较的比较器,要求键值实现了Comparable接口。
  • comparingInt(ToIntFunction<? super T> keyExtractor): 返回一个按照keyExtractor提取出来的int值进行比较的比较器。
  • comparingLong(ToLongFunction<? super T> keyExtractor): 返回一个按照keyExtractor提取出来的long值进行比较的比较器。
  • comparingDouble(ToDoubleFunction<? super T> keyExtractor): 返回一个按照keyExtractor提取出来的double值进行比较的比较器。
  • nullsFirst(Comparator<? super T> comparator): 返回一个将null值视为最小值,并使用comparator进行非null值比较的比较器。
  • nullsLast(Comparator<? super T> comparator): 返回一个将null值视为最大值,并使用comparator进行非null值比较的比较器。

这些方法的引入增加了编程的灵活性,当我们利用Comparator接口编程时,可以更灵活地创建和组合不同的比较规则,而不需要每次都定义一个新的类。例如,我们想要对Person对象按照年龄从大到小排序,如果年龄相同则按照姓名从小到大排序,我们可以使用以下代码:

Arrays.sort(persons, Comparator.comparingInt(Person::getAge).reversed().thenComparing(Person::getName));

下面是一个更详细的应用实例
示例六:电话号码实现Comparator,外部比较器,电话号码排序完整实例

import java.util.Arrays;
import java.util.Comparator;
public class ComparatorTest {public static void main(String[] args) {Telephone[] telephones = new Telephone[]{new Telephone(86, "010", 86180412),new Telephone(86, "010", 56279866),new Telephone(86, "021", 68367160),new Telephone(86, "0574", 86979122),new Telephone(86, "0573", 68787826),new Telephone(86, "021", 42965686)};// 数组排序Arrays.sort(telephones,new TelComparator());// 打印排序后的数组元素Arrays.stream(telephones).forEach(System.out::println);}}class Telephone {private final int countryCode; //国家代码private final String areaCode; //区号private final int number; //电话号码public Telephone(int countryCode, String areaCode, int number) {this.countryCode = countryCode;this.areaCode = areaCode;this.number = number;}public String getAreaCode() {return areaCode;}public int getCountryCode() {return countryCode;}public int getNumber() {return number;}@Overridepublic String toString() {return "PhoneNumber{" +"countryCode=" + countryCode +", areaCode=" + areaCode +", number=" + number +'}';}
}class TelComparator implements Comparator<Telephone> {@Overridepublic int compare(Telephone o1, Telephone o2) {return Comparator.comparingInt(Telephone::getCountryCode).thenComparing(Telephone::getAreaCode).thenComparingInt(Telephone::getNumber).compare(o1, o2);}
}

测试效果图,与前文的一样:
在这里插入图片描述

三、Comparator 和 Comparable 比较

Comparable是可排序接口;若一个类实现了Comparable接口,就意味着“该类是可排序的”。
而Comparator是比较器接口;我们若要对某个类进行排序,而该类又没有实现Comparable可排序接口。则可以通过创建一个“该类的比较器”来进行排序。

Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

(一)应用场景的不同

  • 适用于Comparable接口的场景:

当一个类的自然比较顺序是固定的,并且在整个应用程序中都适用时,使用 Comparable 是一个好的选择。例如,对于一些基本数据类型的包装类(如 Integer、Double 等),它们都实现了 Comparable 接口,具有自然的比较顺序。
如果一个类需要在集合中进行排序,并且希望使用默认的排序方式,那么实现 Comparable 接口可以方便地实现这个需求。例如,使用 Collections.sort(List list)方法对一个包含实现了 Comparable 接口的对象的列表进行排序。

  • 适用于Comparator接口的场景

当需要根据不同的条件对同一个类的对象进行比较时,Comparator 非常有用。例如,对于一个 Student 类,可以根据姓名、年龄和成绩等不同的属性进行排序,通过定义不同的 Comparator 实现类来实现不同的比较策略。
在些情形可能无法修改被比较的类的源代码,这时可以使用 Comparator 来提供外部的比较方式。
Comparator 还可以用于临时改变对象的比较顺序,而不影响类的自然比较顺序。

(二)灵活性和可扩展性

  • Comparable接口的灵活性和扩展性不佳,但代码更简洁更直观

通常实现 Comparable 接口的类比较逻辑固定在类内部。其比较方式是固定的,不会轻易改变。例如,基本数据类型的包装类(如 Integer、Double 等)。如果需要修改比较逻辑,就需要修改类的代码。
但是,由于 Comparable 是类的自然比较方式,它在一些场景下可以提供更简洁的代码和更直观的使用方式。

  • Comparator接口的灵活性和扩展性更佳

Comparator 提供了更好的灵活性和可扩展性。可以根据不同的需求定义多个 Comparator 实现类,在不同的场景下使用不同的比较策略。
可以在程序中动态地选择不同的 Comparator 来实现不同的排序需求,而不需要修改被比较的类的代码。

(三)性能方面比较

  • Comparable

由于 Comparable 的比较逻辑是在类内部实现的,因此在一些情况下可能会有更好的性能。例如,在使用 Collections.sort 方法对一个已经实现了 Comparable 接口的列表进行排序时,排序算法可以直接调用对象的 compareTo 方法,而不需要进行额外的方法调用。

  • Comparator

使用 Comparator 进行比较可能会涉及到额外的方法调用和对象创建,因此在性能上可能会略逊于 Comparable。但是,在大多数情况下,这种性能差异是可以忽略不计的。

综上所述,Comparable 和 Comparator 在 Java 中都是用于实现对象比较的重要工具。它们在定义和实现方式、使用场景、灵活性和可扩展性以及性能等方面存在着一些区别。在实际编程中,应根据具体的需求选择合适的方式来实现对象的比较,以提高代码的可读性、可维护性和性能。

参考文献:

  • Java 中 Comparable 和 Comparator 比较
  • Java中Comparable接口和Comparator接口的使用比较
  • Java Comparator接口的介绍与使用

http://www.mrgr.cn/news/69108.html

相关文章:

  • 2024中国数据安全企业全景图和典型数据安全产品案例集
  • 【freertos】FreeRTOS时间管理
  • 量子计算与人工智能的交汇:科技未来的新引擎
  • DDRPHY数字IC后端设计实现系列专题之数字后端floorplanpowerplan设计
  • redisson内存泄漏问题排查
  • mac终端使用pytest执行iOS UI自动化测试方法
  • 深入理解指针3
  • 3242. 设计相邻元素求和服务
  • 运维规范心得
  • 【linux】再谈网络基础(二)
  • 如何判断 Hive 表是内部表还是外部表
  • C#入门 017 字段,属性,索引器,常量
  • 深入 MyBatis-Plus 插件:解锁高级数据库功能
  • ProcessBuilder调用脚本执行
  • Qt使用属性树(QtProPertyBrowser)时,引用报错#include “QtTreePropertyBrowser“解决方案
  • 星期-时间范围选择器 滑动选择时间 最小粒度 vue3
  • 浅谈web性能测试
  • 智能问答系统流程详解:多轮对话与模型训练的技术要点及案例
  • Unet++改进11:添加MLCA||轻量级的混合本地信道注意机制
  • Linux可视化工具cockpit
  • tar | 打包 | 压缩 | 文件搜索 | 常用命令(二)
  • 浮点数转4字节数组在线转换工具
  • Python内置函数1详解案例
  • Python 基础语法 二维列表
  • Sigrity SPEED2000 Power Ground Noise Simulation模式如何进行电源阻抗仿真分析操作指导(一)-无电容
  • 【贪心】【哈希】个人练习-Leetcode-1296. Divide Array in Sets of K Consecutive Numbers