赞
踩
实际开发中,对业务数据按照指定规则进行排序是很常见的。比如说,“按照记录的创建日期降序排序”、“按照App的应用率从大到小排序”… 在编码过程中,我们
一般在两个层面排序入手:其一、SQL脚本;其二、服务应用层面(通俗来讲就是Java代码层面)。当然,本人探讨的核心是其二。
假如有一组学生列表,学生对象具有id、姓名、所在年级、出生日期、身高字段。需对这一组学生列表进行以下规则进行排序。
学生模型
static class Student { private String id; private String name; private Integer grade; private LocalDate birthday; private Integer height; public Student() { } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getGrade() { return grade; } public void setGrade(Integer grade) { this.grade = grade; } public LocalDate getBirthday() { return birthday; } public void setBirthday(LocalDate birthday) { this.birthday = birthday; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } @Override public String toString() { return "Student{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", grade=" + grade + ", birthday=" + birthday + ", height=" + height + '}'; } }
模拟真实的测试数据
private static List<Student> mockData() { List<Student> list = new ArrayList<Student>(); Student s1 = new Student(); s1.setId("s0001"); s1.setName("张三"); s1.setGrade(6); s1.setBirthday(LocalDate.of(2009, 10, 1)); s1.setHeight(150); list.add(s1); Student s2 = new Student(); s2.setId("s0002"); s2.setName("李四"); s2.setGrade(4); s2.setBirthday(LocalDate.of(2008, 6, 28)); s2.setHeight(143); list.add(s2); Student s3 = new Student(); s3.setId("s0003"); s3.setName("王文武"); s3.setGrade(null); s3.setBirthday(LocalDate.of(2012, 1, 9)); s3.setHeight(112); list.add(s3); Student s4 = new Student(); s4.setId("s0004"); s4.setName("赵佳佳"); s4.setGrade(6); s4.setBirthday(null); s4.setHeight(109); list.add(s4); Student s5 = new Student(); s5.setId("s0005"); s5.setName("甲家"); s5.setGrade(6); s5.setBirthday(LocalDate.of(2009, 10, 1)); s5.setHeight(169); list.add(s5); Student s6 = new Student(); s6.setId("s0006"); s6.setName("李四"); s6.setGrade(4); s6.setBirthday(LocalDate.of(2008, 7, 6)); s6.setHeight(170); list.add(s6); Student s7 = new Student(); s7.setId("s0007"); s7.setName("小华"); s7.setGrade(6); s7.setBirthday(null); s7.setHeight(null); list.add(s7); return list; }
代码
public class App { public static void main(String[] args) { List<Student> students = mockData(); // 排序前 System.out.println(students); Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { int gradeCompare = o1.getGrade().compareTo(o2.getGrade()); if (0 != gradeCompare) { return gradeCompare; } int birthdayCompare = o1.getBirthday().compareTo(o2.getBirthday()); if (0 != birthdayCompare) { return birthdayCompare; } return o1.getHeight().compareTo(o2.getHeight()); } }); // 排序后 System.out.println(students); } }
运行结果
[Student{id='s0001', name='张三', grade=6, birthday=2009-10-01, height=150}, Student{id='s0002', name='李四', grade=4, birthday=2008-06-28, height=143}, Student{id='s0003', name='王文武', grade=null, birthday=2012-01-09, height=112}, Student{id='s0004', name='赵佳佳', grade=6, birthday=null, height=109}, Student{id='s0005', name='甲家', grade=6, birthday=2009-10-01, height=169}, Student{id='s0006', name='李四', grade=4, birthday=2008-07-06, height=170}, Student{id='s0007', name='小华', grade=6, birthday=null, height=null}]
Exception in thread "main" java.lang.NullPointerException
at com.gyoomi.basics.sort.App$1.compare(App.java:36)
at com.gyoomi.basics.sort.App$1.compare(App.java:32)
at java.util.TimSort.countRunAndMakeAscending(TimSort.java:356)
at java.util.TimSort.sort(TimSort.java:220)
at java.util.Arrays.sort(Arrays.java:1512)
at java.util.ArrayList.sort(ArrayList.java:1462)
at java.util.Collections.sort(Collections.java:177)
at com.gyoomi.basics.sort.App.main(App.java:31)
我们发现:按照现有的排序规则进行排列时,控制台抛出异常。究其原因,因为我们某些学生排序字段是null,所以导致我们拿到null值进行compareTo,所以报错。
解决方法
Lambda表达式
public class App { public static void main(String[] args) { List<Student> students = mockData(); // 排序前 System.out.println(students); students.sort((o1, o2) -> { int gradeCompare = o1.getGrade().compareTo(o2.getGrade()); if (0 != gradeCompare) { return gradeCompare; } int birthdayCompare = o1.getBirthday().compareTo(o2.getBirthday()); if (0 != birthdayCompare) { return birthdayCompare; } return o1.getHeight().compareTo(o2.getHeight()); }); // 排序后 System.out.println(students); } }
运行结果同上。
流收集器
public class App { public static void main(String[] args) { List<Student> students = mockData(); // 排序前 System.out.println(students); Collections.sort(students, Comparator.comparing(Student::getGrade) .thenComparing(Student::getBirthday) .thenComparing(Student::getHeight)); // 排序后 System.out.println(students); } }
运行结果同上。我们发现Java8中并没有解决null值字段比较。这是比较蛋疼的。其中我还尝试了Comparator#nullsFirst()
一样存在类似的问题。感觉很鸡肋。所以当我们要排序的字段可能存在null值,我们也不推荐这种方法,
引入依赖
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
代码
public class App { public static void main(String[] args) { List<Student> students = mockData(); // 排序前 System.out.println(students); Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return ComparisonChain.start() .compare(o2.getGrade(), o1.getGrade(), Ordering.natural().nullsLast()) .compare(o1.getBirthday(), o2.getBirthday(), Ordering.natural().nullsFirst()) .compare(o2.getHeight(), o1.getHeight(), Ordering.natural().nullsLast()) .result(); } }); // 排序后 System.out.println(students); } }
运行结果
[Student{id='s0001', name='张三', grade=6, birthday=2009-10-01, height=150}, Student{id='s0002', name='李四', grade=4, birthday=2008-06-28, height=143}, Student{id='s0003', name='王文武', grade=null, birthday=2012-01-09, height=112}, Student{id='s0004', name='赵佳佳', grade=6, birthday=null, height=109}, Student{id='s0005', name='甲家', grade=6, birthday=2009-10-01, height=169}, Student{id='s0006', name='李四', grade=4, birthday=2008-07-06, height=170}, Student{id='s0007', name='小华', grade=6, birthday=null, height=null}]
[Student{id='s0002', name='李四', grade=4, birthday=2008-06-28, height=143}, Student{id='s0006', name='李四', grade=4, birthday=2008-07-06, height=170}, Student{id='s0007', name='小华', grade=6, birthday=null, height=null}, Student{id='s0004', name='赵佳佳', grade=6, birthday=null, height=109}, Student{id='s0005', name='甲家', grade=6, birthday=2009-10-01, height=169}, Student{id='s0001', name='张三', grade=6, birthday=2009-10-01, height=150}, Student{id='s0003', name='王文武', grade=null, birthday=2012-01-09, height=112}]
小结
Guava的运行机制:默认情况下,空值的值应较小。对于空字段,您必须为Guava提供一个额外的指令,在这种情况下该怎么做。如果您想做特定的事情,但是通常您想要默认的大小写(即1,a,b,z,null),这是一种灵活的机制。
引入依赖
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
代码
public class App { public static void main(String[] args) { List<Student> students = mockData(); // 排序前 System.out.println(students); ComparatorChain<Object> cc = new ComparatorChain<>(); cc.addComparator(new BeanComparator<>("grade")); cc.addComparator(new ReverseComparator<>(new BeanComparator<>("birthday"))); cc.addComparator(new BeanComparator<>("height")); students.sort(cc); // 排序后 System.out.println(students); } }
结果
[Student{id='s0001', name='张三', grade=6, birthday=2009-10-01, height=150}, Student{id='s0002', name='李四', grade=4, birthday=2008-06-28, height=143}, Student{id='s0003', name='王文武', grade=null, birthday=2012-01-09, height=112}, Student{id='s0004', name='赵佳佳', grade=6, birthday=null, height=109}, Student{id='s0005', name='甲家', grade=6, birthday=2009-10-01, height=169}, Student{id='s0006', name='李四', grade=4, birthday=2008-07-06, height=170}, Student{id='s0007', name='小华', grade=6, birthday=null, height=null}]
Exception in thread "main" java.lang.NullPointerException
at org.apache.commons.collections.comparators.ComparableComparator.compare(ComparableComparator.java:92)
at org.apache.commons.beanutils.BeanComparator.internalCompare(BeanComparator.java:238)
at org.apache.commons.beanutils.BeanComparator.compare(BeanComparator.java:165)
at org.apache.commons.collections4.comparators.ComparatorChain.compare(ComparatorChain.java:281)
at java.util.TimSort.countRunAndMakeAscending(TimSort.java:356)
at java.util.TimSort.sort(TimSort.java:220)
at java.util.Arrays.sort(Arrays.java:1512)
at java.util.ArrayList.sort(ArrayList.java:1462)
at com.gyoomi.basics.sort.App.main(App.java:38)
小结
我们发现上述代码运行后,同样会抛出空指针异常的错误。这还是由于某些的排序字段为null导致的结果。解决方法同上。
此外,我们发现这种写法简洁,但是如果通过使用字符串(没有类型安全性,自动重构)而失去对字段的直接引用,则更容易出错。现在,如果重命名了字段,则编译器甚至不会报告问题。此外,由于此解决方案使用反射,因此排序速度要慢得多。
优点 | 缺点 | |
---|---|---|
Java8前写法 | 自带,无需引入额外组件 | 不能处理null的情况 |
Java8写法 | 自带,无需引入额外组件。功能稍微强大一点 | 不能处理null的情况 |
Apache Commons Collections | 相比这java自带的简洁 | 不能处理null的情况 |
Guava | 灵活,可以处理各种复杂情况的排序,可以处理null的情况 | 需要额外的学习和引入成本 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。