返回

深入解析Java 8 Comparator comparing方法类型参数

java

Java 8 Comparator comparing 静态方法中的类型参数深入解析

Java 8 Comparator 接口中的 comparing 静态方法提供了一种简洁的方式来构建比较器,可以基于对象的某个属性进行排序。这个方法的方法签名中使用了上下界通配符 ? super? extends, 理解它们对于充分利用 Java 的泛型特性至关重要。

问题分析

comparing 方法的签名如下:

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
    return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

其中 Function<? super T, ? extends U> keyExtractor 是关键。 为什么不直接使用 Function<T, U> keyExtractor?要理解这一点,需要分别分析上下界通配符的作用。

? extends U 的作用

  • ? extends U 表示 keyExtractor 返回的类型必须是 UU 的子类型。 这保证了返回的类型具有 Comparable 特性, 因为 U 被限制为 Comparable<? super U>
  • 若使用 Function<T,U>, 则意味着 keyExtractor 必须精确地返回类型 U , 而不是 U 的子类型。这会降低代码的灵活性。 例如, 如果有一个 Person 类, Employee 类继承了 Person 类, 并且 Person 实现了 Comparable 接口, 那么 keyExtractor 可以返回 Employee 类型, 但如果使用 Function<T,Person>, 则无法接受返回 Employee 类型的 keyExtractor

? super T 的作用

  • ? super T 表示 keyExtractor 接受的参数类型可以是 TT 的超类型。 这增加了方法的适用范围。
  • 设想一下, 如果有一个 Comparator<Person> , 自然也希望它能用来比较 Employee 对象(因为 EmployeePerson 的子类型)。如果 keyExtractor 的参数类型限定为 T , 即 Function<T, U>, 就无法实现这种需求。 通过使用 ? super TkeyExtractor 可以接受 Person 或其任何超类型作为参数, 从而提高了代码的通用性。

示例说明

为了更具体地说明,看一个无法仅通过 Function<T,U> 实现的例子。假设有如下类:

class Person implements Comparable<Person>{
    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
}

class Employee extends Person{
    private int id;
    public Employee(String name,int id) {
        super(name);
        this.id=id;
    }
    public int getId() {
        return id;
    }
}

现在想创建一个 Comparator<Employee> , 基于 Person 的名称排序。 如果 comparing 方法签名如下:

public static <T, U extends Comparable<U>> Comparator<T> comparing(Function<T, U> keyExtractor)
{
   //...
}

那么以下代码将无法编译:

Comparator<Employee> employeeComparator = Comparator.comparing(Employee::getName); // 编译错误

因为 Employee::getName 返回的是 String , 而需要的类型是 Person (根据 U extends Comparable<U>U 必须实现 Comparable)。虽然 String 实现了 Comparable,但它不是 Person

而使用 ? extends U 后,代码就能正常工作,因为 StringObjectComparable 接口的类型参数) 的子类型。同理, 使用 ? super T 可以使 Comparator 更灵活地应用于子类对象。

因此,comparing 方法签名中使用 ? super T? extends U 提升了代码的通用性和灵活性。

完整示例

下面是一个使用 comparing 方法的完整示例, 展示如何根据父类的属性对子类进行排序:


import java.util.Comparator;

class Person implements Comparable<Person>{
    private String name;

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

    public String getName() {
        return name;
    }

    @Override
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
}

class Employee extends Person{
    private int id;

    public Employee(String name,int id) {
        super(name);
        this.id=id;
    }

    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                 ", name='" + getName() + '\'' +
                '}';
    }
}

public class ComparatorDemo {
    public static void main(String[] args) {
        Employee e1 = new Employee("John",2);
        Employee e2 = new Employee("Alice",1);
        Employee e3 = new Employee("Mike",3);

        Comparator<Employee> employeeNameComparator = Comparator.comparing(Person::getName);

        System.out.println("排序前:");
        System.out.println(e1);
        System.out.println(e2);
        System.out.println(e3);

        java.util.List<Employee> employees = java.util.Arrays.asList(e1, e2, e3);

        employees.sort(employeeNameComparator);

        System.out.println("排序后:");
        employees.forEach(System.out::println);
    }
}

操作步骤

  1. 保存上述代码为 ComparatorDemo.java
  2. 编译代码: javac ComparatorDemo.java
  3. 运行代码: java ComparatorDemo

输出结果

排序前:
Employee{id=2, name='John'}
Employee{id=1, name='Alice'}
Employee{id=3, name='Mike'}
排序后:
Employee{id=1, name='Alice'}
Employee{id=2, name='John'}
Employee{id=3, name='Mike'}

程序首先创建三个 Employee 对象,然后使用 Comparator.comparing(Person::getName) 创建一个基于 Personname 属性的 Comparator。 接着对 Employee 列表进行排序,并打印排序前后的结果,可以看到 Employee 对象按照 name 属性(从父类 Person 继承)进行了排序。

相关资源

此篇文章深入讲解了Java 8 Comparator 接口中 comparing 静态方法的类型参数设计原理, 通过示例代码清晰地解释了上下界通配符 ? super? extends 在实际应用中的作用和必要性, 并提供了代码示例和详细的操作步骤。希望可以帮助读者更深刻地理解 Java 泛型。