深入解析Java 8 Comparator comparing方法类型参数
2024-12-16 01:56:46
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
返回的类型必须是U
或U
的子类型。 这保证了返回的类型具有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
接受的参数类型可以是T
或T
的超类型。 这增加了方法的适用范围。- 设想一下, 如果有一个
Comparator<Person>
, 自然也希望它能用来比较Employee
对象(因为Employee
是Person
的子类型)。如果keyExtractor
的参数类型限定为T
, 即Function<T, U>
, 就无法实现这种需求。 通过使用? super T
,keyExtractor
可以接受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
后,代码就能正常工作,因为 String
是 Object
(Comparable
接口的类型参数) 的子类型。同理, 使用 ? 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);
}
}
操作步骤
- 保存上述代码为
ComparatorDemo.java
。 - 编译代码:
javac ComparatorDemo.java
- 运行代码:
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)
创建一个基于 Person
的 name
属性的 Comparator
。 接着对 Employee
列表进行排序,并打印排序前后的结果,可以看到 Employee
对象按照 name
属性(从父类 Person
继承)进行了排序。
相关资源
此篇文章深入讲解了Java 8 Comparator
接口中 comparing
静态方法的类型参数设计原理, 通过示例代码清晰地解释了上下界通配符 ? super
和 ? extends
在实际应用中的作用和必要性, 并提供了代码示例和详细的操作步骤。希望可以帮助读者更深刻地理解 Java 泛型。