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个重载方法,区别如下。

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

  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.  

JDK8新特性:使用stream、Comparator和Method Reference实现集合的优雅排序
如果Student没有实现Comparable接口,效果如下:

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

接下来测试,都不要求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对象。
JDK8新特性:使用stream、Comparator和Method Reference实现集合的优雅排序

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

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

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

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

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

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

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

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

如果我们先按照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. }

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

上一篇:C++内存管理之shared_ptr


下一篇:U盘安装Centos6.2