Java比较器 Comparator 在排序中的应用

Java比较器 Comparator 在排序中的应用

基于IJava编辑

在计算机科学中,排序比较是数据处理的核心操作之一。无论是对数据进行排序、查找还是过滤,都需要一种机制来确定元素之间的相对顺序。Java 提供了强大的工具来实现这一功能,其中之一就是 Comparator 接口。本文档旨在详细记录 Comparator 在 Java 中的应用,特别是它在集合(如 Set, List, Map)和数组中的使用方法。

1. 定义

Comparator 是一个接口,定义了如何比较两个对象的顺序。它通常用于需要自定义排序规则的场合,尤其是在集合类(如 List 和 Set)中。Comparator 接口只有一个方法 compare(T o1, T o2),用于比较两个对象 o1 和 o2 的顺序。
下面是 Comparator 接口的基本定义:

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

方法详解

compare(T o1, T o2):此方法用于比较两个对象 o1 和 o2 的顺序。返回值的含义如下:

  • 如果 o1 小于 o2,则返回负整数,表明第一个对象应该排在第二个对象之前;
  • 如果 o1 等于 o2,则返回零,则表明两个对象相等;
  • 如果 o1 大于 o2,则返回正整数,则表明第一个对象应该排在第二个对象之后。

equals(Object obj):此方法是从 Object 类继承来的,并不是 Comparator 接口的一部分。它用于判断两个比较器是否等价,即它们是否会产生相同的排序结果。

实现原理

Comparator 接口的主要作用是在集合操作中提供一种外部排序机制。通常,集合框架中的排序操作会调用比较器的 compare 方法来确定元素之间的相对顺序。

常用的 Comparator 静态工厂方法

  1. comparing(Function<T, U> keyExtractor)

    • 根据提供的 keyExtractor 函数来提取键进行比较。
  2. comparingInt(Function<T, Integer> keyExtractor)

    • 根据提供的 keyExtractor 函数提取 int 类型的键进行比较。
  3. comparingLong(Function<T, Long> keyExtractor)

    • 根据提供的 keyExtractor 函数提取 long 类型的键进行比较。
  4. comparingDouble(Function<T, Double> keyExtractor)

    • 根据提供的 keyExtractor 函数提取 double 类型的键进行比较。
  5. naturalOrder()

    • 返回一个自然顺序的比较器,适用于实现了 Comparable 接口的类型。
  6. reversedOrder()

    • 返回一个逆序的比较器,适用于实现了 Comparable 接口的类型。
  7. reversed()

    • 反转现有的比较器。
  8. thenComparing(Comparator<? super T> other)

    • 在当前比较器的基础上添加另一个比较器作为次级比较器。
// 定义 Person 类
public class Person {
    private String name;
    private int age;

    public Person(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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35),
            new Person("David", 30)
        );

        // 创建升序比较器(按年龄)
        Comparator<Person> byAgeAscending = Comparator.comparingInt(Person::getAge);

        // 创建降序比较器(按年龄)
        Comparator<Person> byAgeDescending = byAgeAscending.reversed();

        // 创建多条件比较器(先按年龄升序,再按名字升序)
        Comparator<Person> byAgeThenName = Comparator.comparingInt(Person::getAge)
                                                    .thenComparing(Comparator.comparing(Person::getName));

        // 创建自然顺序比较器(按名字)
        Comparator<Person> byNameNaturalOrder = Comparator.comparing(Person::getName);

        // 创建逆序比较器(按名字)
        Comparator<Person> byNameReversedOrder = byNameNaturalOrder.reversed();

        // 使用升序比较器对列表进行排序
        List<Person> sortedByAgeAscending = people.stream()
                                                  .sorted(byAgeAscending)
                                                  .collect(Collectors.toList());

        // 使用降序比较器对列表进行排序
        List<Person> sortedByAgeDescending = people.stream()
                                                   .sorted(byAgeDescending)
                                                   .collect(Collectors.toList());

        // 使用多条件比较器对列表进行排序
        List<Person> sortedByAgeThenName = people.stream()
                                                 .sorted(byAgeThenName)
                                                 .collect(Collectors.toList());

        // 使用自然顺序比较器对列表进行排序
        List<Person> sortedByNameNaturalOrder = people.stream()
                                                      .sorted(byNameNaturalOrder)
                                                      .collect(Collectors.toList());

        // 使用逆序比较器对列表进行排序
        List<Person> sortedByNameReversedOrder = people.stream()
                                                       .sorted(byNameReversedOrder)
                                                       .collect(Collectors.toList());

        // 打印排序结果
        System.out.println("Original list: \n" + people);
        System.out.println("Sorted by age ascending: \n" + sortedByAgeAscending);
        System.out.println("Sorted by age descending: \n" + sortedByAgeDescending);
        System.out.println("Sorted by age then name: \n" + sortedByAgeThenName);
        System.out.println("Sorted by name natural order: \n" + sortedByNameNaturalOrder);
        System.out.println("Sorted by name reversed order: \n" + sortedByNameReversedOrder);
    }
}

Main.main(new String[] {})
Original list: 
[Person{name='Alice', age=30}, Person{name='Bob', age=25}, Person{name='Charlie', age=35}, Person{name='David', age=30}]
Sorted by age ascending: 
[Person{name='Bob', age=25}, Person{name='Alice', age=30}, Person{name='David', age=30}, Person{name='Charlie', age=35}]
Sorted by age descending: 
[Person{name='Charlie', age=35}, Person{name='Alice', age=30}, Person{name='David', age=30}, Person{name='Bob', age=25}]
Sorted by age then name: 
[Person{name='Bob', age=25}, Person{name='Alice', age=30}, Person{name='David', age=30}, Person{name='Charlie', age=35}]
Sorted by name natural order: 
[Person{name='Alice', age=30}, Person{name='Bob', age=25}, Person{name='Charlie', age=35}, Person{name='David', age=30}]
Sorted by name reversed order: 
[Person{name='David', age=30}, Person{name='Charlie', age=35}, Person{name='Bob', age=25}, Person{name='Alice', age=30}]
  • Comparator.comparingInt(Person::getAge):创建一个按年龄升序的比较器。
  • byAgeAscending.reversed():创建一个按年龄降序的比较器。
  • Comparator.comparingInt(Person::getAge).thenComparing(Comparator.comparing(Person::getName)):创建一个多条件比较器,先按年龄升序,如果年龄相同则按名字升序。
  • Comparator.comparing(Person::getName):创建一个按名字自然顺序的比较器。
  • byNameNaturalOrder.reversed():创建一个按名字逆序的比较器。

通过这些方法,您可以灵活地对 Person 对象列表进行各种排序操作。希望这能帮助您更好地理解和使用 Comparator

注意事项

  • 当使用 Comparator 时,必须确保 compare 方法的实现是合理的,即它应该满足比较关系的传递性和对称性。
  • 如果 Comparator 比较的对象可能为 null,需要特别注意处理 null 值的情况。
  • 在多线程环境中使用 Comparator 时,应确保其线程安全。

2. 比较对象数组

在 Java 中,可以通过实现 Comparator 接口来创建一个比较器,通过对象属性的比较进行排序。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Person[] people = new Person[]{
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        };

        // 使用自定义 Person 类定义 Comparator
        Arrays.sort(people, new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                return Integer.compare(p1.getAge(), p2.getAge());
            }
        });

        // 打印排序后的数组
        System.out.println("Sorted array by age using anonymous inner class: " );
        for(Person temp:people){
            System.out.println(temp.toString());
        }
    }
}

// [IJava] 调用main函数,查看 main()函数执行结果 在Jupyter notebook中的输出信息
Main.main(new String [] {})
Sorted array by age using anonymous inner class: 
Person{name='Bob', age=25}
Person{name='Alice', age=30}
Person{name='Charlie', age=35}

使用 Lambda 表达式

从 Java 8 开始,可以使用 Lambda 表达式来简化 Comparator 的实现。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Person[] people = new Person[]{
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 35)
        };

        // 使用 Lambda 表达式定义 Comparator
        Arrays.sort(people, (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));

        // 使用 Lambda 表达式定义多条件的 Comparator
        Arrays.sort(people, Comparator.comparingInt(Person::getAge) // 按照年龄排序
                .thenComparingInt(p -> p.getName().length()) // 按名字的长度排序
                .reversed()); // 反转年龄排序

        // 打印排序后的数组
        System.out.println("Sorted array by age using anonymous inner class: " );
        for(Person temp:people){
            System.out.println(temp.toString());
        }
    }
}

Main.main(new String[] {})
Sorted array by age using anonymous inner class: 
Person{name='Bob', age=25}
Person{name='Alice', age=30}
Person{name='Charlie', age=35}

3. Comparator 的应用场景

应用于 List

在 List 中,可以使用 Collections.sort(List list, Comparator<? super T> c) 方法来对列表进行排序。

import java.util.ArrayList;
import java.util.Collections;

public class Main {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));

        // 使用 Lambda 表达式简化代码
        Collections.sort(people, (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge()));

        // 打印排序后的列表
        System.out.println("Sorted list by age using Lambda: " );
        for(Person temp:people){
            System.out.println(temp.toString());
        }
    }
}


Main.main(new String[] {})
Sorted list by age using Lambda: 
Person{name='Bob', age=25}
Person{name='Alice', age=30}
Person{name='Charlie', age=35}

应用于 Set

虽然 Set 本身不支持排序,但 TreeSet 类实现了 SortedSet 接口,允许使用 Comparator 来控制元素的排序。

import java.util.TreeSet;
import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        TreeSet<Person> people = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person p1, Person p2) {
                return Integer.compare(p1.getAge(), p2.getAge());
            }
        });

        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));

        // 打印排序后的集合
        System.out.println("Sorted set by age: " );
        for(Person temp:people){
            System.out.println(temp.toString());
        }
    }
}

Main.main(new String[] {})
Sorted set by age: 
Person{name='Bob', age=25}
Person{name='Alice', age=30}
Person{name='Charlie', age=35}

应用于 Map

Map 本身不支持排序,但如果需要对 Map 的键或值进行排序,可以使用 TreeMap 并提供一个 Comparator。

import java.util.TreeMap;
import java.util.Comparator;

public class Main {
    public static void main(String[] args) {
        TreeMap<String, Integer> map = new TreeMap<>(new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                return s1.length() - s2.length();
            }
        });

        map.put("apple", 1);
        map.put("banana", 2);
        map.put("cherry", 3);

        // 打印排序后的映射
        System.out.println("Sorted map by key length: " );
        // 遍历 Map 的键值对 
        // 因为 TreeMap 是基于红黑树实现的,它保证了键的有序性,但同时也会自动去除重复的键。因此后来插入的键值对将会覆盖前面的键值对
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

Main.main(new String[] {})
Sorted map by key length: 
apple: 1
banana: 3

Comparator 接口在 Java 中提供了强大的排序功能,允许开发者自定义对象的比较逻辑。无论是集合还是数组,都可以通过实现 Comparator 接口来控制排序顺序。掌握 Comparator 的使用方法,对于开发高质量的应用程序至关重要。

posted @ 2024-11-04 20:01  黑心老魔  阅读(1533)  评论(0)    收藏  举报