• JDK8新特性:使用stream、Comparator和Method Reference实现集合的优雅排序


    大家对java接口Comparator和Comparable都不陌生,JDK8里面Comparable还和以前一样,没有什么改动;但是Comparator在之前基础上增加了很多static和default方法。本文主要结合JDK的stream编程,学习下Comparator。阅读本文需要一些前置知识,可以参考如下文章。

    JDK8新特性:接口的静态方法和默认方法
    http://blog.csdn.net/aitangyong/article/details/54134385

     

    JDK8新特性:函数式接口@FunctionalInterface的使用说明

    http://blog.csdn.net/aitangyong/article/details/54137067

     

    JDK8新特性:lambda入门
    http://blog.csdn.net/aitangyong/article/details/54317539

    JDK8新特性:使用Method References实现方法复用,简化lambda表达式
    http://blog.csdn.net/aitangyong/article/details/54586197

     

    可以使用Stream.sort对集合进行排序,sort有2个重载方法,区别如下。


    1. // Student实现Comparable接口,默认按照id升序排列
    2. public class Student implements Comparable<Student>{
    3.  
    4. private int id;
    5.  
    6. private int age;
    7.  
    8. private String name;
    9.  
    10. private Address address;
    11.  
    12. public Student(int id, int age, String name, Address address) {
    13. this.id = id;
    14. this.age = age;
    15. this.name = name;
    16. this.address = address;
    17. }
    18.  
    19. public int getId() {
    20. return id;
    21. }
    22.  
    23. public void setId(int id) {
    24. this.id = id;
    25. }
    26.  
    27. public int getAge() {
    28. return age;
    29. }
    30.  
    31. public void setAge(int age) {
    32. this.age = age;
    33. }
    34.  
    35. public String getName() {
    36. return name;
    37. }
    38.  
    39. public void setName(String name) {
    40. this.name = name;
    41. }
    42.  
    43. public Address getAddress() {
    44. return address;
    45. }
    46.  
    47. public void setAddress(Address address) {
    48. this.address = address;
    49. }
    50.  
    51. @Override
    52. public String toString() {
    53. return "Student [id=" + id + ", age=" + age + ", name=" + name + ", address=" + address + "]";
    54. }
    55.  
    56. @Override
    57. public int compareTo(Student o) {
    58. return this.id - o.id;
    59. }
    60.  
    61. }

     

    stream().sorted()/Comparator.naturalOrder()/Comparator.reverseOrder(),要求元素必须实现Comparable接口。

    1. import java.util.ArrayList;
    2. import java.util.Comparator;
    3. import java.util.List;
    4. import java.util.stream.Collectors;
    5.  
    6. public class TestComparator {
    7.  
    8. public static void main(String[] args) {
    9. List<Student> students = buildStudents();
    10.  
    11. // 按照默认顺序排序
    12. List<Student> ascList1 = students.stream().sorted().collect(Collectors.toList());
    13. System.out.println(ascList1);
    14.  
    15. // 按照自然序排序(其实就是默认顺序)
    16. List<Student> ascList2 = students.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());
    17. System.out.println(ascList2);
    18.  
    19. // 按照默认顺序的相反顺序排序
    20. List<Student> descList = students.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    21. System.out.println(descList);
    22.  
    23. }
    24.  
    25. private static List<Student> buildStudents() {
    26. List<Student> students = new ArrayList<>();
    27. students.add(new Student(10, 20, "aty", new Address("d")));
    28. students.add(new Student(1, 22, "qun", new Address("c")));
    29. students.add(new Student(1, 26, "Zen", new Address("b")));
    30. students.add(new Student(5, 23, "aty", new Address("a")));
    31. return students;
    32. }
    33.  
    34. }
    35.  
    36.  


    如果Student没有实现Comparable接口,效果如下:


     

    接下来测试,都不要求Student实现Comparable接口,这里直接给出Student和Address实体类。

    1. public class Student {
    2.  
    3. private int id;
    4.  
    5. private int age;
    6.  
    7. private String name;
    8.  
    9. private Address address;
    10.  
    11. public Student(int id, int age, String name, Address address) {
    12. this.id = id;
    13. this.age = age;
    14. this.name = name;
    15. this.address = address;
    16. }
    17.  
    18. public int getId() {
    19. return id;
    20. }
    21.  
    22. public void setId(int id) {
    23. this.id = id;
    24. }
    25.  
    26. public int getAge() {
    27. return age;
    28. }
    29.  
    30. public void setAge(int age) {
    31. this.age = age;
    32. }
    33.  
    34. public String getName() {
    35. return name;
    36. }
    37.  
    38. public void setName(String name) {
    39. this.name = name;
    40. }
    41.  
    42. public Address getAddress() {
    43. return address;
    44. }
    45.  
    46. public void setAddress(Address address) {
    47. this.address = address;
    48. }
    49.  
    50. @Override
    51. public String toString() {
    52. return "Student [id=" + id + ", age=" + age + ", name=" + name + ", address=" + address + "]";
    53. }
    54.  
    55. }
    1. public class Address {
    2. private String address;
    3.  
    4. public Address(String address) {
    5. super();
    6. this.address = address;
    7. }
    8.  
    9. public String getAddress() {
    10. return address;
    11. }
    12.  
    13. public void setAddress(String address) {
    14. this.address = address;
    15. }
    16.  
    17. @Override
    18. public String toString() {
    19. return "Address [address=" + address + "]";
    20. }
    21.  
    22.  
    23. }

     

    Comparator.comparing(Function keyExtractor)生成1个Comparator对象,要求keyExtractor.apply()返回值一定要实现Comparable接口。比如下面代码extractIdWay1和extractIdWay2都是等价的,从Student对象中提取id属性,而id是int类型(Integer实现了Comparable)

    1. import java.util.ArrayList;
    2. import java.util.Comparator;
    3. import java.util.List;
    4. import java.util.function.Function;
    5. import java.util.stream.Collectors;
    6.  
    7. public class TestComparator {
    8.  
    9. public static void main(String[] args) {
    10. List<Student> students = buildStudents();
    11.  
    12. // 使用lambda表达式创建Function对象
    13. Function<Student, Integer> extractIdWay1 = (student) -> student.getId();
    14.  
    15. // 使用方法引用简化lambda
    16. Function<Student, Integer> extractIdWay2 = Student::getId;
    17.  
    18. // Comparator.comparing(Function keyExtractor)
    19. Comparator<Student> byId = Comparator.comparing(extractIdWay2);
    20.  
    21. // 升序
    22. List<Student> ascList = students.stream().sorted(byId).collect(Collectors.toList());
    23. System.out.println(ascList);
    24.  
    25. // 降序
    26. List<Student> descList = students.stream().sorted(byId.reversed()).collect(Collectors.toList());
    27. System.out.println(descList);
    28.  
    29. }
    30.  
    31. private static List<Student> buildStudents() {
    32. List<Student> students = new ArrayList<>();
    33. students.add(new Student(10, 20, "aty", new Address("d")));
    34. students.add(new Student(1, 22, "qun", new Address("c")));
    35. students.add(new Student(1, 26, "Zen", new Address("b")));
    36. students.add(new Student(5, 23, "aty", new Address("a")));
    37. return students;
    38. }
    39.  
    40. }

     

    由于Student.getAddress()返回的对象没有实现Comparable接口,所以不能通过Comparator.comparing()创建一个Comparator对象。


     

    如果我们想安装Address(没有实现Comparable接口)排序怎么办呢?使用另一种形式的comparing方法:


    1. import java.util.ArrayList;
    2. import java.util.Comparator;
    3. import java.util.List;
    4. import java.util.stream.Collectors;
    5.  
    6. public class TestComparator {
    7.  
    8. public static void main(String[] args) {
    9. List<Student> students = buildStudents();
    10.  
    11. Comparator<Address> cmpAddr = Comparator.comparing(Address::getAddress);
    12. Comparator<Student> byAddress = Comparator.comparing(Student::getAddress, cmpAddr);
    13. List<Student> sortedAddressList = students.stream().sorted(byAddress).collect(Collectors.toList());
    14. System.out.println(sortedAddressList);
    15. }
    16.  
    17. private static List<Student> buildStudents() {
    18. List<Student> students = new ArrayList<>();
    19. students.add(new Student(10, 20, "aty", new Address("d")));
    20. students.add(new Student(1, 22, "qun", new Address("c")));
    21. students.add(new Student(1, 26, "Zen", new Address("b")));
    22. students.add(new Student(5, 23, "aty", new Address("a")));
    23. return students;
    24. }
    25.  
    26. }

     

    这种形式的comparing()接收2个参数,第一个参数提取要排序的key,第二个参数指定排序的Comparator。自己指定比较器,可以灵活定制比较逻辑。比如,我们想实现字符串不区分大小写比较。

    1. //getName()返回String本身已经实现了Comparable,但是我们可以自己传递一个不区分大小写的比较器
    2. Comparator<Student> byName = Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER);
    3. List<Student> sortedNameList = students.stream().sorted(byName).collect(Collectors.toList());
    4. System.out.println(sortedNameList);


    comparingDouble()、comparingLong()、comparingInt()不过是comparing()更具体的版本,使用方式相同。

    1. public static void main(String[] args) {
    2. List<Student> students = buildStudents();
    3.  
    4. Comparator<Student> byAge1 = Comparator.comparingInt(Student::getAge);
    5. Comparator<Student> byAge2 = Comparator.comparing(Student::getAge);
    6. List<Student> sortedAgeList1 = students.stream().sorted(byAge1).collect(Collectors.toList());
    7. List<Student> sortedAgeList2 = students.stream().sorted(byAge2).collect(Collectors.toList());
    8. System.out.println(sortedAgeList1);
    9. System.out.println(sortedAgeList2);
    10. }
    11.  
    12. private static List<Student> buildStudents() {
    13. List<Student> students = new ArrayList<>();
    14. students.add(new Student(10, 20, "aty", new Address("d")));
    15. students.add(new Student(1, 22, "qun", new Address("c")));
    16. students.add(new Student(1, 26, "Zen", new Address("b")));
    17. students.add(new Student(5, 23, "aty", new Address("a")));
    18. return students;
    19. }


    Comparator.nullsFirst()和Comparator.nullsLast(),前面我们创建的Student列表中没有null,如果有null的话,上面的代码都会抛异常。而这2个方法就是用来处理null的,一个认为null比所有非null都小,一个认为比所有都大。

    1. public class TestComparator {
    2.  
    3. public static void main(String[] args) {
    4. List<Student> students = buildStudents();
    5. Comparator<Student> nullNotAllowed = Comparator.comparing(Student::getId);
    6. Comparator<Student> allowNullComparator = Comparator.nullsFirst(nullNotAllowed);
    7.  
    8. // 正常排序
    9. List<Student> result1 = students.stream().sorted(allowNullComparator).collect(Collectors.toList());
    10. System.out.println(result1);
    11.  
    12. // 抛异常
    13. List<Student> result2 = students.stream().sorted(nullNotAllowed).collect(Collectors.toList());
    14. System.out.println(result2);
    15.  
    16. }
    17.  
    18. private static List<Student> buildStudents() {
    19. List<Student> students = new ArrayList<>();
    20. students.add(new Student(10, 20, "aty", new Address("d")));
    21. students.add(new Student(1, 22, "qun", new Address("c")));
    22. students.add(new Student(1, 26, "Zen", new Address("b")));
    23. students.add(new Student(5, 23, "aty", new Address("a")));
    24. students.add(null);
    25. return students;
    26. }
    27.  
    28. }




    至此Comparator的static方法已经介绍完毕,接下来我们看下它的default方法。

    reversed()前面已经介绍了,返回一个新的比较器(排序顺序相反)

    thenComparing()系列方法与comparing()使用方法类似




     

    如果我们先按照id排序,id相等的话再按照name排序,那么可以这样写。

    1. public static void main(String[] args) {
    2. List<Student> students = buildStudents();
    3.  
    4. // id升序
    5. Comparator<Student> byIdASC = Comparator.comparing(Student::getId);
    6.  
    7. // named不分区大小写降序
    8. Comparator<Student> byNameDESC = Comparator.comparing(Student::getName, String.CASE_INSENSITIVE_ORDER)
    9. .reversed();
    10.  
    11. // 联合排序
    12. Comparator<Student> finalComparator = byIdASC.thenComparing(byNameDESC);
    13.  
    14. List<Student> result = students.stream().sorted(finalComparator).collect(Collectors.toList());
    15. System.out.println(result);
    16. }
    17.  
    18. private static List<Student> buildStudents() {
    19. List<Student> students = new ArrayList<>();
    20. students.add(new Student(10, 20, "aty", new Address("d")));
    21. students.add(new Student(1, 22, "qun", new Address("c")));
    22. students.add(new Student(1, 26, "Zen", new Address("b")));
    23. students.add(new Student(5, 23, "aty", new Address("a")));
    24. return students;
    25. }


  • 相关阅读:
    Verilog学习笔记基本语法篇(七)········ 生成块
    Verilog学习笔记基本语法篇(六)········ 循环语句
    Verilog学习笔记基本语法篇(五)········ 条件语句
    Verilog学习笔记基本语法篇(四)·········块语句
    Verilog学习笔记基本语法篇(三)·········赋值语句(待补充)
    Verilog学习笔记基本语法篇(二)·········运算符
    Verilog学习笔记基本语法篇(一)·········数据类型
    甲乙类功率放大电路介绍及特点
    JVM虚拟机系列(二)虚拟机的逻辑结构
    JVM虚拟机系列(一)类的加载
  • 原文地址:https://www.cnblogs.com/snake23/p/9407171.html
Copyright © 2020-2023  润新知