• Comparator接口与Lambda表达式


    引言

    在你学习过Lambda表达式和java的函数引用之后,你就可以进一步了解Comparator这个接口了,它提供了很多有用的方法,以及很多函数式接口的参数供你使用,本质的目的是为了让你可以尽可能简单的编写一个排序方法。

    注:本文末尾提供完整的代码。

    不使用Lambda表达式的排序方法

    假设我们有一些Person对象,每个对象都有一个String的名字,而我们希望对这个Person数组或者集合按照姓名的长度进行排序,我们会想到什么方法呢?

    我们会很自然的想到让Person对象继承Comparable接口,并重写compare方法,使得比较两个Person对象的方式是根据姓名长度:

    1. class Person implements Comparable {
    2. private String name;
    3. public Person(String name) {
    4. this.name = name;
    5. }
    6. @Override
    7. public int compareTo(Person o) {
    8. return this.name.length() - o.name.length();
    9. }
    10. }

    但是这会有一个问题,就是如果我们今后对Person所在的数组或者集合希望有多种排序方式的时候,这种方式就无法实现多种排序方式。

    因此基础扎实的同学就可能会想到,我们可以自定义比较器,然后通过传递不同的自定义比较器对象去实现不同的排序方式:

    1. public class LambdaTest {
    2. public static void main(String[] args){
    3. Person[] list = new Person[3];
    4. list[0] = new Person("Zhu","Ya","Xuan");
    5. list[1] = new Person("Yi","Wen","Nian");
    6. list[2] = new Person("Fu","Xiao","Yu");
    7. // 手动创建比较器并使用
    8. Arrays.sort(list, new PersonLengthComparator());
    9. }
    10. }
    11. class PersonLengthComparator implements Comparator {
    12. @Override
    13. public int compare(Person o1, Person o2) {
    14. return Integer.compare(o1.getName().length(),o2.getName().length());
    15. }
    16. }

    但是我们会发现,这实在是太麻烦了,每次都要单独写一个辅助类去完成一个不同功能的排序策略。

    那么有没有一种方法,可以让我们避免写这么多复杂的代码呢?

    为简洁而生Lambda

    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()));

    Comparator接口的一些有用方法

    具体的代码已经放在了下方的完整代码中,以下仅简述特性:

    1.可以对comparing方法提取的键,再提供一个比较器。

    2.使用.thenComparing可以提供多级比较。

    3.可以提供一个Comparator.nullsFirst适配器,它会修改现有的比较器。

    注:使用nullsFirst时,一定要为这个方法内提供一个修饰比较器参数,如果除了对null值处理的要求以外没有其他要求了,也要提供一个Comparator.naturalOrder()占位。

    4.有一个提供反向比较器的静态方法Comparator.reverseOrder()。

    5.由于我们只是要提供一个Comparator比较器对象,所以上述内容对数组和集合同样适用。

    6.排序操作如果对一个不可变集合使用,将会抛出UnsupportedOperationException异常

    注:该异常通常意味着你在尝试修改一个不可修改的集合。(详情见下方代码)

    1. import javax.swing.Timer;
    2. import java.util.*;
    3. public class LambdaTest {
    4. public static void main(String[] args){
    5. Person[] list = new Person[3];
    6. list[0] = new Person("Zhu","Ya","Xuan");
    7. list[1] = new Person("Yi","Wen","Nian");
    8. list[2] = new Person("Fu","Xiao","Yu");
    9. // 手动创建比较器并使用
    10. Arrays.sort(list, new PersonLengthComparator());
    11. // 使用lambda表达式省略自定义Comparator比较器的过程
    12. Arrays.sort(list, (a,b) -> a.getName().length() - b.getName().length());
    13. Arrays.sort(list, Comparator.comparingInt(a -> a.getName().length()));
    14. // 可以对comparing方法提取的键,再提供一个比较器
    15. Arrays.sort(list, Comparator.comparing(Person::getName, (o1, o2) -> o1.length() - o2.length()));
    16. Arrays.sort(list, Comparator.comparing(Person::getName, Comparator.comparingInt(String::length)));
    17. // 可以使用comparingInt方法配合lambda表达式优化上面的语句,同上
    18. Arrays.sort(list, Comparator.comparingInt(p -> p.getName().length()));
    19. // 使用.thenComparing可以提供多级比较
    20. Arrays.sort(list, Comparator.comparingInt((Person p) -> p.getFirstName().length())
    21. .thenComparingInt(p -> p.getMiddleName().length()));
    22. // Comparator.nullsFirst是一个适配器,它会修改现有的比较器
    23. // (你也可以认为comparing方法的第二个参数都是这样的适配器)
    24. // 如果键函数可能为空(此处为Person::getMiddleName)
    25. // 就可以提供nullsFirst适配器,使得做比较的第一个对象为null时也可以正常比较
    26. // Comparator.naturalOrder()表示正常的比较(使用nullsFirst时该参数不可省略)
    27. Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
    28. Comparator.nullsFirst(Comparator.naturalOrder())));
    29. // 你自然也可以在里面添加一个普通的比较器
    30. Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
    31. Comparator.nullsFirst(Comparator.comparingInt(String::length))));
    32. // 还有一个提供反向比较器的静态方法Comparator.reverseOrder()
    33. Arrays.sort(list, Comparator.comparing(Person::getMiddleName,
    34. Comparator.reverseOrder()));
    35. // 由于我们只是要提供一个Comparator比较器对象,所以上述内容对数组和集合同样适用
    36. List personList = new ArrayList<>(Arrays.asList(list));
    37. personList.sort(Comparator.comparing(Person::getMiddleName, Comparator.reverseOrder()));
    38. // 要特别注意,以下操作在上述排序中会抛出 UnsupportedOperationException 异常
    39. // List personList = Arrays.stream(list).toList();
    40. // 该异常通常意味着你在尝试修改一个不可修改的集合
    41. // 出现该异常的原因是toList这种带to开头的方法返回的是一个不可修改的视图
    42. System.out.println(Arrays.toString(list));
    43. // lambda表达式的另一个运用
    44. var timer = new Timer(1000, e -> System.out.println("每间隔1秒输出1行"));
    45. timer.start();
    46. while(true){}
    47. }
    48. }
    49. class PersonLengthComparator implements Comparator {
    50. @Override
    51. public int compare(Person o1, Person o2) {
    52. return Integer.compare(o1.getName().length(),o2.getName().length());
    53. }
    54. }
    55. class Person implements Comparable {
    56. public Person(String firstName, String middleName, String lastName) {
    57. this.firstName = firstName;
    58. this.middleName = middleName;
    59. this.lastName = lastName;
    60. this.name = firstName + middleName + lastName;
    61. }
    62. private String firstName;
    63. private String middleName;
    64. private String lastName;
    65. private String name;
    66. @Override
    67. public String toString() {
    68. return name;
    69. }
    70. public String getFirstName() {
    71. return firstName;
    72. }
    73. public void setFirstName(String firstName) {
    74. this.firstName = firstName;
    75. }
    76. public String getMiddleName() {
    77. return middleName;
    78. }
    79. public void setMiddleName(String middleName) {
    80. this.middleName = middleName;
    81. }
    82. public String getLastName() {
    83. return lastName;
    84. }
    85. public void setLastName(String lastName) {
    86. this.lastName = lastName;
    87. }
    88. public String getName() {
    89. return name;
    90. }
    91. public void setName(String name) {
    92. this.name = name;
    93. }
    94. @Override
    95. public int compareTo(Person o) {
    96. return this.name.length() - o.name.length();
    97. }
    98. }

  • 相关阅读:
    Redis数据结构之——ziplist
    【论文简述】 Point-MVSNet:Point-Based Multi-View Stereo Network(ICCV 2019)
    LeetCode每日一练 —— CM11 链表分割
    三栏布局,中间自适应
    关于选择智能小车学习
    ClickHouse Senior Course Ⅱ
    定时器setInterval()和clearInterval()的使用
    Git分支管理,运维知道吗?
    【excel实战】-- 批量提取批准&多重区域复制粘贴
    iOS13之后获取状态栏高度的方法
  • 原文地址:https://blog.csdn.net/Ares_moran/article/details/134277903