基于comparator排序
在 java8 之前,都是通过实现comparator
接口完成排序,比如:
new comparator<student>() { @override public 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中定义排序反转
import com.google.common.collect.lists; import org.junit.jupiter.api.test; import java.util.comparator; import java.util.list; import static org.junit.jupiter.api.assertions.assertequals; class student { private string name; private int age; public student(string name, int age) { this.name = name; this.age = age; } public string getname() { return name; } public int getage() { return age; } @override public 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 && name.equals(student.name); } @override public int hashcode() { return name.hashcode() + age; } @override public string tostring() { return "student{" + "name='" + name + '\'' + ", age=" + age + '}'; } } public class comparatortest { @test void sortedusingcomparatorreverse() { final list<student> students = lists.newarraylist( new student("tom", 10), new student("jerry", 12) ); // 使用comparator.comparing的重载方法,传入comparator.reverseorder()实现倒序 students.sort(comparator.comparing(student::getname, comparator.reverseorder())); // 验证排序结果 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; } @override public 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
,组合模式就是这么好用,一层不够再套一层。
以上就是java使用lambda表达式实现排序功能的实现代码的详细内容,更多关于java lambda排序功能的资料请关注代码网其它相关文章!
发表评论