网站建设项目建议书,营销型网站的基本模板,做门户网站需要学什么软件,什么是网络营销方案我们在系统开发过程中#xff0c;对数据排序是很常见的场景。一般来说#xff0c;我们可以采用两种方式#xff1a;借助存储系统#xff08;SQL、NoSQL、NewSQL 都支持#xff09;的排序功能#xff0c;查询的结果即是排好序的结果查询结果为无序数据#xff0c;在内存中…我们在系统开发过程中对数据排序是很常见的场景。一般来说我们可以采用两种方式借助存储系统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 ComparatorStudent() {Overridepublic int compare(Student h1, Student h2) {return h1.getName().compareTo(h2.getName());}
};这里展示的是匿名内部类的定义如果是通用的对比逻辑可以直接定义一个实现类。使用起来也比较简单如下就是应用Test
void baseSortedOrigin() {final ListStudent students Lists.newArrayList(new Student(Tom, 10),new Student(Jerry, 12));Collections.sort(students, new ComparatorStudent() {Overridepublic 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 ListStudent 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 ListStudent 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 ListStudent 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 ListStudent 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 ListStudent 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 表达式还引入了一个全新的流式 APIStream API其中也有sorted方法用于流式计算时排序元素可以传入Comparator实现排序逻辑Test
void streamSorted() {final ListStudent students Lists.newArrayList(new Student(Tom, 10),new Student(Jerry, 12));final ComparatorStudent comparator (h1, h2) - h1.getName().compareTo(h2.getName());final ListStudent sortedStudents students.stream().sorted(comparator).collect(Collectors.toList());Assertions.assertEquals(sortedStudents.get(0), new Student(Jerry, 12));
}同样的我们可以通过 Lambda 简化书写Test
void streamSortedUsingComparator() {final ListStudent students Lists.newArrayList(new Student(Tom, 10),new Student(Jerry, 12));final ComparatorStudent comparator Comparator.comparing(Student::getName);final ListStudent sortedStudents students.stream().sorted(comparator).collect(Collectors.toList());Assertions.assertEquals(sortedStudents.get(0), new Student(Jerry, 12));
}倒序排列调转排序判断排序就是根据compareTo方法返回的值判断顺序如果想要倒序排列只要将返回值取返即可Test
void sortedReverseUsingComparator2() {final ListStudent students Lists.newArrayList(new Student(Tom, 10),new Student(Jerry, 12));final ComparatorStudent 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 ListStudent students Lists.newArrayList(new Student(Tom, 10),new Student(Jerry, 12));final ComparatorStudent 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 ListStudent 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 ListStudent students Lists.newArrayList(new Student(Tom, 10),new Student(Jerry, 12));final ComparatorStudent comparator (h1, h2) - h2.getName().compareTo(h1.getName());final ListStudent sortedStudents students.stream().sorted(comparator).collect(Collectors.toList());Assertions.assertEquals(sortedStudents.get(0), new Student(Tom, 10));
}Test
void streamReverseSortedUsingComparator() {final ListStudent students Lists.newArrayList(new Student(Tom, 10),new Student(Jerry, 12));final ListStudent 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 ListStudent students Lists.newArrayList(null,new Student(Snoopy, 12),null);Assertions.assertThrows(NullPointerException.class,() - students.sort(Comparator.comparing(Student::getName)));
}所以我们需要考虑这些场景。元素是 null 的笨拙实现最先想到的就是判空Test
void sortedNullNoNPE() {final ListStudent 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 NullComparatorT implements ComparatorT {private final ComparatorT real;NullComparator(Comparator? super T real) {this.real (ComparatorT) 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 ListStudent 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 ListStudent 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 ListStudent students Lists.newArrayList(new Student(null, 10),new Student(Snoopy, 12),null);final ComparatorStudent nullsLast Comparator.nullsLast(Comparator.nullsLast( // 1Comparator.comparing(Student::getName,Comparator.nullsLast( // 2Comparator.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 表达式实现各种排序逻辑新增的语法糖真香。往期推荐MyBatis 中为什么不建议使用 where 11HashMap 中的一个“坑”聊聊sql优化的15个小技巧