咱们在零碎开发过程中,对数据排序是很常见的场景。一般来说,咱们能够采纳两种形式:
借助存储系统(SQL、NoSQL、NewSQL 都反对)的排序功能,查问的后果即是排好序的后果
查问后果为无序数据,在内存中排序。
明天要说的是第二种排序形式,在内存中实现数据排序。
首先,咱们定义一个根底类,前面咱们将依据这个根底类演示如何在内存中排序。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;private int age;@Overridepublic boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Student student = (Student) o; return age == student.age && Objects.equals(name, student.name);}@Overridepublic int hashCode() { return Objects.hash(name, age);}
}
复制代码
基于Comparator排序
在 Java8 之前,咱们都是通过实现Comparator接口实现排序,比方:
new Comparator<Student>() {
@Overridepublic int compare(Student h1, Student h2) { return h1.getName().compareTo(h2.getName());}
};
复制代码
这里展现的是匿名外部类的定义,如果是通用的比照逻辑,能够间接定义一个实现类。应用起来也比较简单,如下就是利用:
@Test
void baseSortedOrigin() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student h1, Student h2) { return h1.getName().compareTo(h2.getName()); }});Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
复制代码
这里应用了 Junit5 实现单元测试,用来验证逻辑非常适合。
因为定义的Comparator是应用name字段排序,在 Java 中,String类型的排序是通过单字符的 ASCII 码程序判断的,J排在T的后面,所以Jerry排在第一个。
应用 Lambda 表达式替换Comparator匿名外部类
应用过 Java8 的 Lamdba 的应该晓得,匿名外部类能够简化为 Lambda 表达式为:
Collections.sort(students, (Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));
复制代码
在 Java8 中,List类中减少了sort办法,所以Collections.sort能够间接替换为:
students.sort((Student h1, Student h2) -> h1.getName().compareTo(h2.getName()));
复制代码
依据 Java8 中 Lambda 的类型推断,咱们能够将指定的Student类型简写:
students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
复制代码
至此,咱们整段排序逻辑能够简化为:
@Test
void baseSortedLambdaWithInferring() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));students.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
复制代码
通过静态方法抽取公共的 Lambda 表达式
咱们能够在Student中定义一个静态方法:
public static int compareByNameThenAge(Student s1, Student s2) {
if (s1.name.equals(s2.name)) { return Integer.compare(s1.age, s2.age);} else { return s1.name.compareTo(s2.name);}
}
复制代码
这个办法须要返回一个int类型参数,在 Java8 中,咱们能够在 Lambda 中应用该办法:
@Test
void sortedUsingStaticMethod() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));students.sort(Student::compareByNameThenAge);Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
复制代码
借助Comparator的comparing办法
在 Java8 中,Comparator类新增了comparing办法,能够将传递的Function参数作为比拟元素,比方:
@Test
void sortedUsingComparator() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));students.sort(Comparator.comparing(Student::getName));Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
复制代码
多条件排序
咱们在静态方法一节中展现了多条件排序,还能够在Comparator匿名外部类中实现多条件逻辑:
@Test
void sortedMultiCondition() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12), new Student("Jerry", 13));students.sort((s1, s2) -> { if (s1.getName().equals(s2.getName())) { return Integer.compare(s1.getAge(), s2.getAge()); } else { return s1.getName().compareTo(s2.getName()); }});Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
复制代码
从逻辑来看,多条件排序就是先判断第一级条件,如果相等,再判断第二级条件,顺次类推。在 Java8 中能够应用comparing和一系列thenComparing示意多级条件判断,下面的逻辑能够简化为:
@Test
void sortedMultiConditionUsingComparator() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12), new Student("Jerry", 13));students.sort(Comparator.comparing(Student::getName).thenComparing(Student::getAge));Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
复制代码
这里的thenComparing办法是能够有多个的,用于示意多级条件判断,这也是函数式编程的不便之处。
在Stream中进行排序
Java8 中,岂但引入了 Lambda 表达式,还引入了一个全新的流式 API:Stream API,其中也有sorted办法用于流式计算时排序元素,能够传入Comparator实现排序逻辑:
@Test
void streamSorted() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());final List<Student> sortedStudents = students.stream() .sorted(comparator) .collect(Collectors.toList());Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}
复制代码
同样的,咱们能够通过 Lambda 简化书写:
@Test
void streamSortedUsingComparator() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));final Comparator<Student> comparator = Comparator.comparing(Student::getName);final List<Student> sortedStudents = students.stream() .sorted(comparator) .collect(Collectors.toList());Assertions.assertEquals(sortedStudents.get(0), new Student("Jerry", 12));
}
复制代码
倒序排列
调转排序判断
排序就是依据compareTo办法返回的值判断程序,如果想要倒序排列,只有将返回值取返即可:
@Test
void sortedReverseUsingComparator2() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());students.sort(comparator);Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}
复制代码
能够看到,正序排列的时候,咱们是h1.getName().compareTo(h2.getName()),这里咱们间接倒转过去,应用的是h2.getName().compareTo(h1.getName()),也就达到了取反的成果。在 Java 的Collections中定义了一个java.util.Collections.ReverseComparator外部公有类,就是通过这种形式实现元素反转。
借助Comparator的reversed办法倒序
在 Java8 中新增了reversed办法实现倒序排列,用起来也是很简略:
@Test
void sortedReverseUsingComparator() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));final Comparator<Student> comparator = (h1, h2) -> h1.getName().compareTo(h2.getName());students.sort(comparator.reversed());Assertions.assertEquals(students.get(0), new Student("Tom", 10));
}
复制代码
在Comparator.comparing中定义排序反转
comparing办法还有一个重载办法,java.util.Comparator#comparing(java.util.function.Function<? super T,? extends U>, java.util.Comparator<? super U>),第二个参数就能够传入Comparator.reverseOrder(),能够实现倒序:
@Test
void sortedUsingComparatorReverse() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));students.sort(Comparator.comparing(Student::getName, Comparator.reverseOrder()));Assertions.assertEquals(students.get(0), new Student("Jerry", 12));
}
复制代码
在Stream中定义排序反转
在Stream中的操作与间接列表排序相似,能够反转Comparator定义,也能够应用Comparator.reverseOrder()反转。实现如下:
@Test
void streamReverseSorted() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));final Comparator<Student> comparator = (h1, h2) -> h2.getName().compareTo(h1.getName());final List<Student> sortedStudents = students.stream() .sorted(comparator) .collect(Collectors.toList());Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}
@Test
void streamReverseSortedUsingComparator() {
final List<Student> students = Lists.newArrayList( new Student("Tom", 10), new Student("Jerry", 12));final List<Student> sortedStudents = students.stream() .sorted(Comparator.comparing(Student::getName, Comparator.reverseOrder())) .collect(Collectors.toList());Assertions.assertEquals(sortedStudents.get(0), new Student("Tom", 10));
}
复制代码
null 值的判断
后面的例子中都是有值元素排序,可能笼罩大部分场景,但有时候咱们还是会碰到元素中存在null的状况:
列表中的元素是 null
列表中的元素参加排序条件的字段是 null
如果还是应用后面的那些实现,咱们会碰到NullPointException异样,即 NPE,简略演示一下:
@Test
void sortedNullGotNPE() {
final List<Student> students = Lists.newArrayList( null, new Student("Snoopy", 12), null);Assertions.assertThrows(NullPointerException.class, () -> students.sort(Comparator.comparing(Student::getName)));
}
复制代码
所以,咱们须要思考这些场景。
元素是 null 的蠢笨实现
最先想到的就是判空:
@Test
void sortedNullNoNPE() {
final List<Student> students = Lists.newArrayList( null, new Student("Snoopy", 12), null);students.sort((s1, s2) -> { if (s1 == null) { return s2 == null ? 0 : 1; } else if (s2 == null) { return -1; } return s1.getName().compareTo(s2.getName());});Assertions.assertNotNull(students.get(0));Assertions.assertNull(students.get(1));Assertions.assertNull(students.get(2));
}
复制代码
咱们能够将判空的逻辑抽取出一个Comparator,通过组合形式实现:
class NullComparator<T> implements Comparator<T> {
private final Comparator<T> real;NullComparator(Comparator<? super T> real) { this.real = (Comparator<T>) real;}@Overridepublic int compare(T a, T b) { if (a == null) { return (b == null) ? 0 : 1; } else if (b == null) { return -1; } else { return (real == null) ? 0 : real.compare(a, b); }}
}
复制代码
在 Java8 中曾经为咱们筹备了这个实现。
应用Comparator.nullsLast和Comparator.nullsFirst
应用Comparator.nullsLast实现null在结尾:
@Test
void sortedNullLast() {
final List<Student> students = Lists.newArrayList( null, new Student("Snoopy", 12), null);students.sort(Comparator.nullsLast(Comparator.comparing(Student::getName)));Assertions.assertNotNull(students.get(0));Assertions.assertNull(students.get(1));Assertions.assertNull(students.get(2));
}
复制代码
应用Comparator.nullsFirst实现null在结尾:
@Test
void sortedNullFirst() {
final List<Student> students = Lists.newArrayList( null, new Student("Snoopy", 12), null);students.sort(Comparator.nullsFirst(Comparator.comparing(Student::getName)));Assertions.assertNull(students.get(0));Assertions.assertNull(students.get(1));Assertions.assertNotNull(students.get(2));
}
复制代码
是不是很简略,接下来咱们看下如何实现排序条件的字段是 null 的逻辑。
排序条件的字段是 null
这个就是借助Comparator的组合了,就像是套娃实现了,须要应用两次Comparator.nullsLast,这里列出实现:
@Test
void sortedNullFieldLast() {
final List<Student> students = Lists.newArrayList( new Student(null, 10), new Student("Snoopy", 12), null);final Comparator<Student> nullsLast = Comparator.nullsLast( Comparator.nullsLast( // 1 Comparator.comparing( Student::getName, Comparator.nullsLast( // 2 Comparator.naturalOrder() // 3 ) ) ));students.sort(nullsLast);Assertions.assertEquals(students.get(0), new Student("Snoopy", 12));Assertions.assertEquals(students.get(1), new Student(null, 10));Assertions.assertNull(students.get(2));
}
复制代码
代码逻辑如下:
代码 1 是第一层 null-safe 逻辑,用于判断元素是否为 null;
代码 2 是第二层 null-safe 逻辑,用于判断元素的条件字段是否为 null;
代码 3 是条件Comparator,这里应用了Comparator.naturalOrder(),是因为应用了String排序,也能够写为String::compareTo。如果是简单判断,能够定义一个更加简单的Comparator,组合模式就是这么好用,一层不够再套一层。
文末总结
本文演示了应用 Java8 中应用 Lambda 表达式实现各种排序逻辑,新增的语法糖真香。
青山不改,绿水长流,咱们下次见。
最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163互相学习,咱们会有业余的技术答疑解惑
如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点star:http://github.crmeb.net/u/defu不胜感激 !
PHP学习手册:https://doc.crmeb.com
技术交换论坛:https://q.crmeb.com