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. }



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM