Java比较器

一、概述

       在Java中,比较器是一种用于定义对象排序顺序的接口,提供了Comparable和Comparator这两个接口来实现对象的排序。


二、Comparable自然排序

1、Comparable接口

       Comparable接口用于对象的自然排序,可以使用Java内置的排序算法(如Arrays.sort()和Collections.sort())进行排序。接口中只包含一个抽象方法compareTo(T o),用于比较当前对象与指定对象的顺序,返回值是一个整数,表示比较结果,结果的含义如下:

  • 1)、如果返回值 < 0,表示当前对象小于指定对象。
  • 2)、如果返回值 = 0,表示当前对象等于指定对象。
  • 3)、如果返回值 > 0,表示当前对象大于指定对象。

2、Comparable接口的实现类:基本类型的包装类、String、···等都实现了该接口,重写了compareTo()方法给出了比较两个对象大小的方式。

  • 1)、Integer实现自然排序: 
    List<Integer> integers = Arrays.asList(5, 15, 33, 27, 3);
    Collections.sort(integers); // 调用排序方法进行排序
    System.out.println(integers); // 输出[3, 5, 15, 27, 33]
  • 2)、String实现自然排序:
    List<String> stringList = Arrays.asList("Java", "Python", "C++");
    Collections.sort(stringList); // 调用排序方法进行排序
    System.out.println(stringList); // 输出[C++, Java, Python]

3、自定义类实现Comparable实现自然排序:

  • 1)、创建自定义类并实现Comparable接口。
  • 2)、重写compareTo()方法,实现排序逻辑。
public class Person implements Comparable<Person> { // 实现Comparable接口
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 重写compareTo接口实现排序逻辑:先根据年龄,再根据姓名进行排序
     * @param other the object to be compared.
     * @return
     */
    @Override
    public int compareTo(Person other) {
        // 首先比较年龄
        int ageComparison = Integer.compare(this.age, other.age);
        if (ageComparison != 0) {
            return ageComparison; // 如果年龄不同,直接返回年龄的比较结果
        }
        // 如果年龄相同,再比较名称(按字典顺序)
        return this.name.compareTo(other.name);
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    /**
     * 测试排序功能
     * @param args
     */
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Bob", 30),
                new Person("Alice", 25),
                new Person("Charlie", 25)
        );
        // 调用排序方法进行排序:根据年龄升序排序,年龄相同再根据姓名进行升序排序
        Collections.sort(people); 
        System.out.println(people); // 输出[Person{name='Alice', age=25}, Person{name='Charlie', age=25}, Person{name='Bob', age=30}]
    }

}

三、Comparator自定义排序

1、Comparator接口

        Comparator接口用于对象的自定义排序。主要用于对集合中的元素进行排序(如Arrays.sort()和Collections.sort()),常用作为方法的参数。接口中包含一个抽象方法compare(T o1, T o2),用于比较比较两个对象(o1和o2)的顺序,返回值是一个整数,表示比较结果,结果的含义如下:

  • 1)、如果返回值 < 0,表示o1小于o2。
  • 2)、如果返回值 = 0,表示o1等于o2。
  • 3)、如果返回值 > 0,表示o1大于o2。

2、Comparator的使用:主要用于作为方法的参数(如排序方法的参数)使用,有以下使用方式。

2.1 实现Comparator接口的方式

通过定义一个类实现Comparator接口,重写compare()方法实现排序逻辑。

public class AgeComparator implements Comparator<Student> { // 实现Comparator接口
    @Override
    public int compare(Student s1, Student s2) { // 重写compare()方法实现排序逻辑
        return Integer.compare(s1.getAge(), s2.getAge());
    }

    public static void main(String[] args) {
        List<Student> studentList = Arrays.asList(
                new Student("Bob", 30),
                new Student("Alice", 25),
                new Student("Charlie", 25)
        );
        // 实现Comparator接口的方式实现自定义排序
        studentList.sort(new AgeComparator());
        System.out.println(studentList); // 输出[Student{name='Alice', age=25}, Student{name='Charlie', age=25}, Student{name='Bob', age=30}]
    }
}

public class Student {
    private String name;
    private int age;

    public Student(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 "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
2.2 匿名内部类的方式

在以Comparator接口为参数的方法中通过匿名内部类的方式实现排序逻辑。

List<Student> studentList = Arrays.asList(
            new Student("Bob", 30),
            new Student("Alice", 25),
            new Student("Charlie", 25)
    );
// 匿名内部类方式排序
studentList.sort(new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getAge() - o2.getAge(); // o1-o2为升序,o2-o1为降序
    }
});
System.out.println(studentList); // 输出[Student{name='Alice', age=25}, Student{name='Charlie', age=25}, Student{name='Bob', age=30}]
2.3 Lambda表达式的方式

Comparator接口是一个函数式接口,可以使用Lambda表达式的方式来实现排序逻辑。

List<Student> studentList = Arrays.asList(
            new Student("Bob", 30),
            new Student("Alice", 25),
            new Student("Charlie", 25)
    );
// 使用Lambad方式排序:o1-o2为升序,o2-o1为降序
studentList.sort((o1, o2) -> o1.getAge() - o2.getAge()); // 根据年龄升序
System.out.println(studentList); // 输出[Student{name='Alice', age=25}, Student{name='Charlie', age=25}, Student{name='Bob', age=30}]
studentList.sort((o1, o2) -> o2.getAge() - o1.getAge()); // 根据年龄降序
System.out.println(studentList); // 输出[Student{name='Bob', age=30}, Student{name='Alice', age=25}, Student{name='Charlie', age=25}]
2.4 方法引用的方式

逻辑比较简单也可以使用方法引用的方式。

List<Student> studentList = Arrays.asList(
            new Student("Bob", 30),
            new Student("Alice", 25),
            new Student("Charlie", 25)
    );
// 方法引用的方式
studentList.sort(Comparator.comparing(Student::getAge)); // 根据年龄进行升序排序
System.out.println(studentList); // 输出[Student{name='Bob', age=30}, Student{name='Alice', age=25}, Student{name='Charlie', age=25}]
studentList.sort(Comparator.comparing(Student::getName)); // 根据姓名进行升序排序
System.out.println(studentList); // 输出[Student{name='Alice', age=25}, Student{name='Bob', age=30}, Student{name='Charlie', age=25}]

3、Comparator提供的获取比较器的方法

JDK8 Comparator接口引入了一些静态和默认的方法,提供了更多的灵活性,这些方法会返回一个新的比较器。

3.1 default Comparator<T> reversed() :返回当前比较器的逆序版本的比较器。

具体代码如下:

// 1、reversed(): 返回当前比较器的逆序比较器。
List<Student> studentList = Arrays.asList(
        new Student("Bob", 30),
        new Student("Alice", 25),
        new Student("Charlie", 25)
);
System.out.println("初始集合:");
studentList.forEach(System.out::println);
Comparator<Student> comparator = Comparator.comparingInt(Student::getAge);
studentList.sort(comparator);
System.out.println("根据年龄升序排序:");
studentList.forEach(System.out::println);// 输出Student{name='Alice', age=25},Student{name='Charlie', age=25},Student{name='Bob', age=30}
studentList.sort(comparator.reversed());
System.out.println("通过比较器的reversed()方法进行逆序(年龄降序)排序:");
studentList.forEach(System.out::println); // 输出Student{name='Bob', age=30},Student{name='Alice', age=25},Student{name='Charlie', age=25}
3.2 public static <T extends Comparable<? super T>> Comparator<T> reverseOrder():对对象集合进行反向自然排序(实现Comparable接口),返回比较器。

具体代码如下:

// 2、reverseOrder():反向自然排序
List<Person> peopleList = Arrays.asList(
        new Person("Bob", 30),
        new Person("Alice", 25),
        new Person("Charlie", 25)
);
System.out.println("初始集合:");
peopleList.forEach(System.out::println);
// 自然排序
Collections.sort(peopleList);
System.out.println("自然排序:");
peopleList.forEach(System.out::println); // 输出Person{name='Alice', age=25},Person{name='Charlie', age=25},Person{name='Bob', age=30}
// 反向自然排序
peopleList.sort(Comparator.reverseOrder());
System.out.println("反向自然排序:");
peopleList.forEach(System.out::println); // 输出Person{name='Bob', age=30},Person{name='Charlie', age=25},Person{name='Alice', age=25}
3.3 public static <T extends Comparable<? super T>> Comparator<T> naturalOrder():以自然顺序比较可比较的对象,返回一个比较器。效果与Collections.sort()相同。

具体代码如下:

// 3、naturalOrder():以自然顺序比较可比较的对象
List<String> stringList1 = Arrays.asList("Bob", "Alice", "Charlie");
System.out.println("初始集合:" + stringList1); // 输出[Bob, Alice, Charlie]
stringList1.sort(Comparator.naturalOrder());
System.out.println("自然排序的方式:" + stringList1); // 输出[Alice, Bob, Charlie]

List<String> stringList2 = Arrays.asList("Bob", "Alice", "Charlie");
System.out.println("初始集合:" + stringList2); // 输出[Bob, Alice, Charlie]
Collections.sort(stringList2);
System.out.println("Collections.sort()的方式:" + stringList2); // [Alice, Bob, Charlie]
3.4 对null友好的比较器:返回一个对null友好的比较器,主要又nullsFirst()和nullsLast()这两个方法。
  • 1)、static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator):返回一个对null友好的比较器,它认为null小于非null。

  • 2)、static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) :返回一个对null友好的比较器,它认为null大于非nul。

        当两个元素都是空的时候,那么它们就被认为是相等的;当两个元素都是非空的时候,指定的比较器决定了顺序。如果指定的比较器是空的,那么返回的比较器认为所有非空的元素是相等的。具体代码如下:

// 4、对null友好的比较器
List<String> stringList1 = Arrays.asList("Bob", null, "Alice", "Charlie", null);
System.out.println("初始集合:" + stringList1); // 输出[Bob, null, Alice, Charlie, null]
// 1)、nullsFirst():null小于非null的排序
stringList1.sort(Comparator.nullsFirst(Comparator.naturalOrder()));
System.out.println("null小于非null的排序方式:" + stringList1); // 输出[null, null, Alice, Bob, Charlie]
// 2)、对null友好的比较器():null大于非null的排序
stringList1.sort(Comparator.nullsLast(Comparator.naturalOrder()));
System.out.println("null大于非null的排序方式:" + stringList1); // 输出[Alice, Bob, Charlie, null, null]
3.5 静态方法comparing():接受一个函数,该函数从给定的类型中提取一个可比较的排序键,并返回一个通过该排序键进行比较的比较器。有两种形式(重载):
  • 1)、static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor) :传递一个函数,从一个类型T中提取一个可比较的排序键,并返回一个通过该排序键进行比较的比较器。
  • 2)、static <T,U> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator) :传递一个函数和一个比较器,从一个类型T中提取一个排序键,并返回一个比较器,使用指定的比较器对该排序键进行比较。

 具体代码如下:

List<Student> studentList = Arrays.asList(
        new Student("Bob", 30),
        new Student("Alice", 25),
        new Student("Charlie", 25)
);
System.out.println("初始集合:");
studentList.forEach(System.out::println);
studentList.sort(Comparator.comparing(Student::getAge));
System.out.println("只传递可比较的排序键,不自定义比较器:");
studentList.forEach(System.out::println); // 输出Student{name='Alice', age=25},Student{name='Charlie', age=25},Student{name='Bob', age=30}
studentList.sort(Comparator.comparing(Student::getAge, (o1, o2) -> o2.compareTo(o1)));
System.out.println("传递可比较的排序键,而且自定义比较器:");
studentList.forEach(System.out::println); // 输出Student{name='Bob', age=30},Student{name='Alice', age=25},Student{name='Charlie', age=25}
3.6 comparing()方法根据不同数据类型的变种:提供了一种更简洁的写法
  • 1)、static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor):接受一个从类型T中提取一个int排序键的函数,并返回一个通过该排序键进行比较的比较器。
  • 2)、static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor):接受一个从类型T中提取long排序键的函数,并返回一个通过该排序键进行比较的比较器。
  • 3)、static <T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) :接受一个从类型T中提取double排序键的函数,并返回一个通过该排序键进行比较的比较器。

具体代码如下:

// 6、comparing()变种方法
List<Staff> staffList = Arrays.asList(
        new Staff("Bob", 30, 170, 60.1),
        new Staff("Alice", 25, 176, 55.7),
        new Staff("Charlie", 25, 165, 78.5)
);
System.out.println("初始集合:");
staffList.forEach(System.out::println);
// 1、comparingInt():根据int类型字段age进行排序
staffList.sort(Comparator.comparingInt(Staff::getAge));
System.out.println("通过comparingInt()根据int类型字段age进行排序:");
staffList.forEach(System.out::println);
// 2、comparingLong():根据long类型字段height进行排序
staffList.sort(Comparator.comparingLong(Staff::getHeiht));
System.out.println("通过comparingLong()根据long类型字段height进行排序:");
staffList.forEach(System.out::println);
// 3、comparingDouble():根据double类型字段weight进行排序
staffList.sort(Comparator.comparingDouble(Staff::getWeight));
System.out.println("通过comparingDouble()根据double类型字段Weight进行排序:");
staffList.forEach(System.out::println);

public class Staff {
    private String name;
    private int age;
    private long heiht;
    private double weight;
    private Company company;

    public Staff(String name, int age, long heiht, double weight) {
        this.name = name;
        this.age = age;
        this.heiht = heiht;
        this.weight = weight;
    }

    public Staff(String name, int age, long heiht, double weight, Company company) {
        this.name = name;
        this.age = age;
        this.heiht = heiht;
        this.weight = weight;
        this.company = company;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public long getHeiht() {
        return heiht;
    }

    public void setHeiht(long heiht) {
        this.heiht = heiht;
    }

    public double getWeight() {
        return weight;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    @Override
    public String toString() {
        return "Staff{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", heiht=" + heiht +
                ", weight=" + weight +
                ", company=" + company +
                '}';
    }
}

4、多属性排序

        当需要根据对象的多个属性进行排序时,通过thenComparing()方法链式地添加多个比较器。该方法返回一个词表顺序(lexicographic-order)的比较器,该比较器被一个比较器实例调用,使用一组排序键对项目进行排序。有以下三种形式(重载):

  • 1)、default Comparator<T> thenComparing(Comparator<? super T> other) :返回一个词表顺序(lexicographic-order)比较器和另一个比较器。
  • 2)、default <U extends Comparable<? super U>> Comparator<T> thenComparing(Function<? super T,? extends U> keyExtractor) :返回一个词表顺序(lexicographic-order)比较器,其中包含一个提取可比较排序键的函数。
  • 3)、default <U> Comparator<T> thenComparing(Function<? super T,? extends U> keyExtractor, Comparator<? super U> keyComparator) :返回一个词表顺序(lexicographic-order)的比较器,该比较器带有一个函数,可以提取一个键与给定的比较器进行比较。

具体代码如下:

// 7、多属性排序thenComparing()
List<Staff> staffList = Arrays.asList(
    new Staff("Bob", 30, 170, 60.1, new Company("阿里巴巴", "杭州")),
    new Staff("Alice", 25, 176, 55.7, new Company("腾讯", "深圳")),
    new Staff("Charlie", 25, 165, 78.5, new Company("字节跳动", "北京"))
);
// 先根据年龄进行升序排序,再根据体重进行降序排序
staffList.sort(Comparator.comparingInt(Staff::getAge).thenComparing(Comparator.comparing(Staff::getWeight).reversed()));
System.out.println("先根据年龄进行升序排序,再根据体重进行降序排序:");
staffList.forEach(System.out::println);
// 先根据年龄进行排序,再根据身高进行排序
staffList.sort(Comparator.comparingInt(Staff::getAge).thenComparing(Staff::getHeiht));
System.out.println("先根据年龄进行排序,再根据身高进行排序:");
staffList.forEach(System.out::println);
// 先根据年龄进行排序,再根据城市进行排序
staffList.sort(Comparator.comparingInt(Staff::getAge).thenComparing(Staff::getCompany, Comparator.comparing(Company::getCity)));
System.out.println("先根据年龄进行排序,再根据城市进行排序:");
staffList.forEach(System.out::println);

thenComparing()方法根据不同数据类型的变种:提供了一种更简洁的写法。

  • 1)、default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) :返回一个词表顺序(lexicographic-order)比较器,其中包含一个提取int排序键的函数。
  • 2)、default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) :返回一个词表顺序(lexicographic-order)比较器,其中包含一个提取long排序键的函数。
  • 3)、default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) :返回一个词表顺序(lexicographic-order)比较器,其中包含一个提取double排序键的函数。

具体代码如下:

// 8、多属性排序thenComparing()变种
List<Staff> staffList = Arrays.asList(
        new Staff("Bob", 30, 170, 55.7, new Company("阿里巴巴", "杭州")),
        new Staff("Alice", 25, 176, 55.7, new Company("腾讯", "深圳")),
        new Staff("Charlie", 25, 165, 78.5, new Company("字节跳动", "北京"))
);
// 先根据年龄进行排序,再根据身高进行排序
// 1、thenComparingInt()
staffList.sort(Comparator.comparingDouble(Staff::getWeight).thenComparingInt(Staff::getAge));
System.out.println("thenComparingInt():");
staffList.forEach(System.out::println);
// 2、thenComparingLong()
staffList.sort(Comparator.comparingInt(Staff::getAge).thenComparingLong(Staff::getHeiht));
System.out.println("thenComparingLong():");
staffList.forEach(System.out::println);
// 3、thenComparingDouble()
staffList.sort(Comparator.comparingInt(Staff::getAge).thenComparingDouble(Staff::getWeight));
System.out.println("thenComparingDouble:");
staffList.forEach(System.out::println);

5、注意事项

  • 1)、性能:应避免在compare方法中进行耗时的操作,以保持排序操作的效率。
  • 2)、线程安全:比较器本身是无状态的,是线程安全的。但如果比较器依赖于外部状态,则在使用时需要注意线程安全问题。

四、Comparable与Comparator的区别:

1、设计理念

  • Comparable用来定义对象的自然排序规则。相当于“内比较器”,通过让类实现它,然后重写compareTo()方法来将比较逻辑嵌入到类的内部。该类的实例就可以相互比较,从而确定它们的排序顺序。
  • Comparator用来定义对象的自定义排序规则。相当于"外比较器",它的实现与要比较的类是相互独立的,通常在方法中通过lambda表达式或匿名类实现接口,无需在类上实现。允许开发者在不修改要比较的类的情况下定义比较逻辑。

2、灵活性

  • Comparable的排序方式依赖于类本身,具有一定的局限性,只能实现一种排序方式。
  • Comparator提供了更高的灵活性,允许根据不同的需求定义不同的比较逻辑

3、适用场景

  •  Comparable适用于定义类的自然排序规则,即当类的实例在排序时应该遵循的默认规则。如当一个类有多个字段,但只有一个字段是用于排序的主要字段时,可以使用Comparable接口。
  • Comparator适用于定义类的自定义排序规则,即当类的实例在排序时需要遵循的特定规则。如当一个类有多个字段,并且需要根据不同的字段或组合字段进行排序,可以使用Comparator接口。

4、优先级 

  • 如果一个类同时实现了Comparable接口和提供了Comparator比较器,并且同时用于排序操作,那么Comparator的排序规则将覆盖Comparable的自然排序规则。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值