Java-比较与排序

5444 字
14 分钟

每日一言

Pirates are evil? The Marines are righteous?… Justice will prevail, you say? But of course, it will! Whoever wins this war becomes justice! — Donquixote Doflamingo from One Piece

Comparable接口

在java.lang中定义了Comparable接口,其定义如下:

package java.lang;

public interface Comparable<T> {
    public int compareTo(T o);
}

该接口只有一个方法就是comparaTo方法。继承了该接口的类可以通过实现comparable方法来定义该类的比较方法。从而可以使用排序库来方便的实现自定义类的排序问题。

public class Student implements Comparable<Student> {
    private String name;
    private int score;
  
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
  
    @Override
    public int compareTo(Student other) {
        // 按分数降序排列
        return other.score - this.score;
  
        // 如果要按分数升序排列
        // return this.score - other.score;
  
        // 如果要先按分数再按姓名
        // if (this.score != other.score) {
        //     return this.score - other.score;
        // }
        // return this.name.compareTo(other.name);
    }
  
    // getter, setter, toString等方法...
}

compareTo方法

compareTo方法的定义如下:

public int compareTo(T o);

T o 代表传入进来与之比较的参数

返回值的含义:

  • 返回负数:表示当前对象小于参数对象
  • 返回0:表示当前对象等于参数对象
  • 返回正数:表示当前对象大于参数对象

注意:在实现compareTo方法时,应该注意与 equals方法的一致性,即 x.equals(y)true时,x.compareTo(y)应返回0。

自然排序

在Comparable中实现的比较方法在java中被称为自然排序,这是一个类自身所固有的、默认的比较方式。

实现compareTo方法后,就有许多排序和排序和查找库可以供我们使用:

Arrays类中的排序方法

import java.util.Arrays;

// 对数组进行排序
Arrays.sort(array);  // 数组元素必须实现Comparable接口

// 对数组部分排序
Arrays.sort(array, fromIndex, toIndex);  // 排序指定范围内的元素

Collections类中的排序方法

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

// 对List集合进行排序
Collections.sort(list);  // 集合元素必须实现Comparable接口

// 使用二分查找(需要先排序)
int index = Collections.binarySearch(list, key);

TreeSet和TreeMap

import java.util.TreeSet;
import java.util.TreeMap;

// TreeSet自动排序
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("banana");
treeSet.add("apple");
treeSet.add("orange");
// 自动按字母顺序排列: [apple, banana, orange]

// TreeMap键自动排序
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(3, "Three");
treeMap.put(1, "One");
treeMap.put(2, "Two");
// 键自动按数字顺序排列

PriorityQueue优先队列

import java.util.PriorityQueue;

// 优先队列根据compareTo方法确定优先级
PriorityQueue<String> queue = new PriorityQueue<>();
queue.add("banana");
queue.add("apple");
queue.add("orange");

// poll()方法每次取出最小元素
String first = queue.poll();  // "apple"

自定义排序

与Comparable 不同,Comparator 是用于自定义排序规则的接口,但是不需要修改被比较的类,而是作为一个独立的比较器。

Comparator 定义

java.util 包中,定义如下:

public interface Comparator<T> {
    int compare(T o1, T o2);
}

Comparator的静态方法

Java 8开始,Comparator接口提供了多种静态方法来创建和组合比较器:

方法描述示例
comparing(Function<T,U>)根据指定函数提取的键创建比较器Comparator.comparing(Student::getName)
comparingInt/Long/Double(ToIntFunction)根据提取的基本类型值创建比较器Comparator.comparingInt(Student::getScore)
naturalOrder()返回自然顺序比较器Comparator.naturalOrder()
reverseOrder()返回自然顺序的逆序比较器Comparator.reverseOrder()
nullsFirst(Comparator)修改比较器使null值排在前面Comparator.nullsFirst(comparingInt(Student::getScore))
nullsLast(Comparator)修改比较器使null值排在后面Comparator.nullsLast(comparing(Student::getName))
reversed()返回当前比较器的逆序版本comparing(Student::getScore).reversed()
thenComparing(Comparator)组合两个比较器,当第一个返回0时使用第二个comparing(Student::getScore).thenComparing(Student::getName)
thenComparingInt/Long/Double(ToIntFunction)组合比较器,第二个使用基本类型comparing(Student::getGrade).thenComparingInt(Student::getScore)

创建Comparator

创建Comparator比较器的方法有很多种,我们从最简单学起:

首先假设我们有一个Person类:

class Person {
    private String name;
    private int age;
    private double height;

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

    public String getName() { return name; }
    public int getAge() { return age; }
    public double getHeight() { return height; }

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

创建类方式(传统方式)

import java.util.Comparator;

// 按姓名升序排序的比较器
class PersonNameComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        // String 类本身实现了 Comparable,可以直接用 compareTo
        return p1.getName().compareTo(p2.getName());
    }
}

// 按年龄降序排序的比较器
class PersonAgeDescendingComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        // p2 在前,p1 在后,实现降序
        return p2.getAge() - p1.getAge(); //(注意潜在溢出)
  
    }
}

使用匿名内部类

import java.util.Comparator;

// ... 在需要使用 Comparator 的地方 ...
Comparator<Person> ageComparator = new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.getAge(), p2.getAge()); // 按年龄升序
    }
};

使用Lambda表达式

import java.util.Comparator;

// 按年龄升序
Comparator<Person> ageComparatorLambda = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge());
// 或者更简洁,如果 Person::getAge 返回的是 Comparable 类型 (Integer 是)
// Comparator<Person> ageComparatorLambda = Comparator.comparingInt(Person::getAge); // 见下文

// 按姓名升序
Comparator<Person> nameComparatorLambda = (p1, p2) -> p1.getName().compareTo(p2.getName());
// Comparator<Person> nameComparatorLambda = Comparator.comparing(Person::getName); // 见下文

使用 Comparator 的静态工厂方法 (Java 8+, 非常推荐,功能强大且易读)

import java.util.Comparator;

// 按年龄升序 (使用 comparingInt 优化基本类型)
Comparator<Person> compareByAge = Comparator.comparingInt(Person::getAge);

// 按姓名升序 (使用 comparing)
Comparator<Person> compareByName = Comparator.comparing(Person::getName);

// 按身高降序 (使用 comparingDouble + reversed)
Comparator<Person> compareByHeightDesc = Comparator.comparingDouble(Person::getHeight).reversed();

// 组合比较:先按年龄升序,如果年龄相同,再按姓名升序
Comparator<Person> compareByAgeThenName = Comparator.comparingInt(Person::getAge)
                                                   .thenComparing(Person::getName);

// 组合比较:先按年龄降序,如果年龄相同,再按姓名升序
Comparator<Person> compareByAgeDescThenName = Comparator.comparingInt(Person::getAge).reversed()
                                                        .thenComparing(Person::getName);

// 处理 null 值:将 null 排在最前面,然后按年龄升序
Comparator<Person> compareByAgeNullsFirst = Comparator.nullsFirst(Comparator.comparingInt(Person::getAge));

// 处理 null 值:将 null 排在最后面,然后按姓名升序
Comparator<Person> compareByNameNullsLast = Comparator.nullsLast(Comparator.comparing(Person::getName));

使用Comparator

  1. Collections.sort(List <T> list, Comparator)

  2. Arrays.sort(T[] a, Comparator)

  3. 创建有序集合 (TreeSet,TreeMap) 时指定排序规则

    Comparator<Person> byAge = Comparator.comparingInt(Person::getAge);
    Set<Person> sortedPeople = new TreeSet<>(byAge);