在你学习过Lambda表达式和java的函数引用之后,你就可以进一步了解Comparator这个接口了,它提供了很多有用的方法,以及很多函数式接口的参数供你使用,本质的目的是为了让你可以尽可能简单的编写一个排序方法。
注:本文末尾提供完整的代码。
假设我们有一些Person对象,每个对象都有一个String的名字,而我们希望对这个Person数组或者集合按照姓名的长度进行排序,我们会想到什么方法呢?
我们会很自然的想到让Person对象继承Comparable接口,并重写compare方法,使得比较两个Person对象的方式是根据姓名长度:
- class Person implements Comparable
{ - private String name;
-
- public Person(String name) {
- this.name = name;
- }
-
- @Override
- public int compareTo(Person o) {
- return this.name.length() - o.name.length();
- }
- }
但是这会有一个问题,就是如果我们今后对Person所在的数组或者集合希望有多种排序方式的时候,这种方式就无法实现多种排序方式。
因此基础扎实的同学就可能会想到,我们可以自定义比较器,然后通过传递不同的自定义比较器对象去实现不同的排序方式:
- public class LambdaTest {
- public static void main(String[] args){
- Person[] list = new Person[3];
- list[0] = new Person("Zhu","Ya","Xuan");
- list[1] = new Person("Yi","Wen","Nian");
- list[2] = new Person("Fu","Xiao","Yu");
-
- // 手动创建比较器并使用
- Arrays.sort(list, new PersonLengthComparator());
- }
- }
-
- class PersonLengthComparator implements Comparator
{ - @Override
- public int compare(Person o1, Person o2) {
- return Integer.compare(o1.getName().length(),o2.getName().length());
- }
- }
但是我们会发现,这实在是太麻烦了,每次都要单独写一个辅助类去完成一个不同功能的排序策略。
那么有没有一种方法,可以让我们避免写这么多复杂的代码呢?
lambda是一个可传递的代码块,可以在以后执行一次或者多次。
java会自动根据lambda表达式生成一个目标所需的对象,传递给其他代码使用。
Arrays.sort(list, (a,b) -> a.getName().length() - b.getName().length());
上述这个例子我们可以放心大胆的这样去理解:我们为sort方法提供了一个比较器,这个比较器比较两个对象时,根据第一个对象的name长度减去第二个对象的name长度确认compare比较的结果值,系统在执行时根据这个比较值的结果确认它们的大小。
怎么样,是不是感觉这样的代码语言就跟人类的语言很相似了,这就是lambda的强大之处。
当然,系统的Comparator的还为我们提供了一些有用的方法,可以让我们对上面这个式子的内容做的更好:
Arrays.sort(list, Comparator.comparingInt(a -> a.getName().length()));
具体的代码已经放在了下方的完整代码中,以下仅简述特性:
1.可以对comparing方法提取的键,再提供一个比较器。
2.使用.thenComparing可以提供多级比较。
3.可以提供一个Comparator.nullsFirst适配器,它会修改现有的比较器。
注:使用nullsFirst时,一定要为这个方法内提供一个修饰比较器参数,如果除了对null值处理的要求以外没有其他要求了,也要提供一个Comparator.naturalOrder()占位。
4.有一个提供反向比较器的静态方法Comparator.reverseOrder()。
5.由于我们只是要提供一个Comparator比较器对象,所以上述内容对数组和集合同样适用。
6.排序操作如果对一个不可变集合使用,将会抛出UnsupportedOperationException异常
注:该异常通常意味着你在尝试修改一个不可修改的集合。(详情见下方代码)
- import javax.swing.Timer;
- import java.util.*;
-
- public class LambdaTest {
- public static void main(String[] args){
- Person[] list = new Person[3];
- list[0] = new Person("Zhu","Ya","Xuan");
- list[1] = new Person("Yi","Wen","Nian");
- list[2] = new Person("Fu","Xiao","Yu");
-
-
- // 手动创建比较器并使用
- Arrays.sort(list, new PersonLengthComparator());
- // 使用lambda表达式省略自定义Comparator比较器的过程
- Arrays.sort(list, (a,b) -> a.getName().length() - b.getName().length());
- Arrays.sort(list, Comparator.comparingInt(a -> a.getName().length()));
- // 可以对comparing方法提取的键,再提供一个比较器
- Arrays.sort(list, Comparator.comparing(Person::getName, (o1, o2) -> o1.length() - o2.length()));
- Arrays.sort(list, Comparator.comparing(Person::getName, Comparator.comparingInt(String::length)));
- // 可以使用comparingInt方法配合lambda表达式优化上面的语句,同上
- Arrays.sort(list, Comparator.comparingInt(p -> p.getName().length()));
- // 使用.thenComparing可以提供多级比较
- Arrays.sort(list, Comparator.comparingInt((Person p) -> p.getFirstName().length())
- .thenComparingInt(p -> p.getMiddleName().length()));
-
- // Comparator.nullsFirst是一个适配器,它会修改现有的比较器
- // (你也可以认为comparing方法的第二个参数都是这样的适配器)
- // 如果键函数可能为空(此处为Person::getMiddleName)
- // 就可以提供nullsFirst适配器,使得做比较的第一个对象为null时也可以正常比较
- // Comparator.naturalOrder()表示正常的比较(使用nullsFirst时该参数不可省略)
- Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
- Comparator.nullsFirst(Comparator.naturalOrder())));
- // 你自然也可以在里面添加一个普通的比较器
- Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
- Comparator.nullsFirst(Comparator.comparingInt(String::length))));
- // 还有一个提供反向比较器的静态方法Comparator.reverseOrder()
- Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
- Comparator.reverseOrder()));
-
- // 由于我们只是要提供一个Comparator比较器对象,所以上述内容对数组和集合同样适用
- List
personList = new ArrayList<>(Arrays.asList(list)); - personList.sort(Comparator.comparing(Person::getMiddleName, Comparator.reverseOrder()));
- // 要特别注意,以下操作在上述排序中会抛出 UnsupportedOperationException 异常
- // List
personList = Arrays.stream(list).toList(); - // 该异常通常意味着你在尝试修改一个不可修改的集合
- // 出现该异常的原因是toList这种带to开头的方法返回的是一个不可修改的视图
-
- System.out.println(Arrays.toString(list));
-
- // lambda表达式的另一个运用
- var timer = new Timer(1000, e -> System.out.println("每间隔1秒输出1行"));
- timer.start();
- while(true){}
- }
- }
-
- class PersonLengthComparator implements Comparator
{ -
- @Override
- public int compare(Person o1, Person o2) {
- return Integer.compare(o1.getName().length(),o2.getName().length());
- }
- }
-
- class Person implements Comparable
{ - public Person(String firstName, String middleName, String lastName) {
- this.firstName = firstName;
- this.middleName = middleName;
- this.lastName = lastName;
- this.name = firstName + middleName + lastName;
- }
-
- private String firstName;
- private String middleName;
- private String lastName;
- private String name;
-
- @Override
- public String toString() {
- return name;
- }
-
- public String getFirstName() {
- return firstName;
- }
-
- public void setFirstName(String firstName) {
- this.firstName = firstName;
- }
-
- public String getMiddleName() {
- return middleName;
- }
-
- public void setMiddleName(String middleName) {
- this.middleName = middleName;
- }
-
- public String getLastName() {
- return lastName;
- }
-
- public void setLastName(String lastName) {
- this.lastName = lastName;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- @Override
- public int compareTo(Person o) {
- return this.name.length() - o.name.length();
- }
- }