返回博客列表

11 分钟阅读Yaron

Java中的Comparator比较器——基础篇

概述

本文介绍Java中Comparator比较器的基础用法,包括其作用、核心方法以及不同的使用方式。

示例代码

package com.example.myspringboot.comparator;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Student> students = Arrays.asList(
                new Student("Alice", 22, 178),
                new Student("Bob", 20, 180),
                new Student("Charlie", 23, 190),
                new Student("David", 20, 175),
                new Student("Alan", 20, 175)
        );

        // Java 8 之前的排序方式
        // Comparator<Student> ageComparator = new Comparator<Student>() {
        //     @Override
        //     public int compare(Student s1, Student s2) {
        //         return s1.getAge().compareTo(s2.getAge());
        //     }
        // };

        // Java 8 Lambda表达式
        // students.sort((Student o1,Student o2)->{return o1.getAge().compareTo(o2.getAge());});
        // 简化类型
        // students.sort((o1, o2)->{return o1.getAge().compareTo(o2.getAge());});
        // 如果 Lambda 的方法体只有一行代码,那么大括号 {} 和 return 关键字都可以省略。
        // students.sort((o1,o2)->o1.getAge().compareTo(o2.getAge()));

        // Java 8 方法引用
        // students.sort(Comparator.comparing(Student::getAge));
        // System.out.println("按年龄升序排序");
        // for (Student student : students) {
        //     System.out.println(student);
        // }

        // 多条件排序
        // students.sort(
        //         // 先按照年龄升序
        //         Comparator.comparing(Student::getAge)
        //                 // 年龄相同的情况下,按照身高降序
        //                 .thenComparing(Student::getHeight, Comparator.reverseOrder())
        //                 // 年龄和身高都相同的情况下,按照姓名升序
        //                 .thenComparing(Student::getName)
        // );
        // System.out.println("多条件排序");
        // for (Student student : students) {
        //     System.out.println(student);
        // }
        
        Collections.sort(students);
        for (Student student : students) {
            System.out.println(student);
        }
    }

    @Data
    @AllArgsConstructor
    static class Student implements Comparable<Student> {
        private String name;
        private Integer age;
        private Integer height;

        @Override
        public int compareTo(Student o) {
            // 先按照年龄升序
            int compareTo1 = this.age.compareTo(o.age);
            if (compareTo1 == 0) {
                // 年龄相同的情况下,按照身高降序
                int compareTo2 = o.height.compareTo(this.height);
                if (compareTo2 == 0) {
                    // 年龄和身高都相同的情况下,按照姓名升序
                    return this.name.compareTo(o.name);
                }
                return compareTo2;
            }
            return compareTo1;
        }
    }
}

Comparator比较器的作用

Comparator是Java中的一个常用接口,主要用于排序场景。Comparator比较器的作用是告诉排序算法,两个对象的相对位置关系。排序的本质就是确定两个对象,谁排在前,谁排在后,而Comparator比较器正是来达成这个目的。

为什么需要一个专门的比较器来确定两个对象的位置关系呢?对于字符串、整数等常见的数据类型,我们可以根据字典序、数字自然顺序来进行排序,但是现实世界中,需要排序的对象也包含复杂的业务对象。对于排序算法(例如快速排序、冒泡排序)来说,它们无法直接理解这些复杂对象的内部结构,例如对学员的年龄属性排序,排序算法是无法知道每一个学员的年龄属性是如何获取的。另外,需要升序?还是降序?这些排序算法都是无法知道这完全取决于程序员的意图。 那么排序算法所不知道的这些,我们该如何告知它呢?答案就是通过Comparator比较器来实现“排序意图传递”。

Comparator排序器需要传递哪些意图给排序算法呢?

  1. 需要对哪个属性进行排序,如何获取它们;
  2. 升序还是降序排列。例如students.sort((o1,o2)-> o2.getAge().compareTo(o1.getAge()))通过getAge表明需要根据对象的age属性进行排序,并且o2.getAge().compareTo(o1.getAge())返回值符号决定了最终是降序排列。

比较器的核心方法compare(o1,o2)

@FunctionalInterface
public interface Comparator<T> {
    /**
     * 通过比较两个参数的顺序,根据第一个参数小于、等于、大于第二个参数,
     * 分别返回一个负数、零、正数
     * 
     * @param o1 要比较的第一个对象
     * @param o2 要比较的第二个对象
     * @return 根据第一个参数小于、等于或大于第二个参数,分别返回一个负整数、零或正整数。
     * @throws NullPointerException if an argument is null and this
     *         comparator does not permit null arguments
     * @throws ClassCastException if the arguments' types prevent them from
     *         being compared by this comparator.
     */
    int compare(T o1, T o2);
}

通过查阅JDK的接口文档,我们知道compare(o1,o2)的作用,其核心作用就是通过返回负数、0、正数来告诉排序算法两个对象的相对顺序。

另外,通过代码我们知道Comparator接口是一个函数式接口(有注解@FunctionalInterface),那么在使用的时候我们就可以通过lambda表达式来对其进行简洁、优雅的实现。

如何优雅的使用Comparator比较器

1. Java 8之前:匿名内部类

// Java 8 之前的排序方式
Comparator<Student> ageComparator = new Comparator<Student>() {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.getAge().compareTo(s2.getAge());
    }
};
students.sort(ageComparator);

2. Java 8:Lambda表达式

// Java 8 Lambda表达式
students.sort((Student o1,Student o2)->{return o1.getAge().compareTo(o2.getAge());});

// 省略类型
students.sort((o1, o2)->{return o1.getAge().compareTo(o2.getAge());});

// 如果 Lambda 的方法体只有一行代码,那么大括号 {} 和 return 关键字都可以省略
students.sort((o1,o2)->o1.getAge().compareTo(o2.getAge()));

3. Java 8:方法引用

students.sort(Comparator.comparing(student -> student.getAge()));

// 用方法引用 :: 来进一步简化:
students.sort(Comparator.comparing(Student::getAge));

总结

Comparator的主要作用是告诉排序算法两个对象的相对位置关系,通过核心方法compare(o1, o2)返回负数、0、正数来确定排序顺序。

Comparator有三种使用方式:

  1. 匿名内部类:Java 8之前的传统方式
  2. Lambda表达式:Java 8引入的简洁写法
  3. 方法引用:最简洁优雅的实现方式

其中方法引用Comparator.comparing(Student::getAge)是推荐的写法,代码简洁且易于理解。