基于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排序功能的资料请关注代码网其它相关文章!
发表评论