java开发中,有很多对集合List排序的应用场景,集合中可以是一个基本类型的封装类型,也可以是一个复杂的自定义类的对象。对于有多个属性的对象,有可能依据某个属性来排序,也有可能依据多个属性综合排序,本文主要讲解复杂引用类型集合的排序,包括单属性和多属性排序。本文所有用例均经过验证,方便学习和速查,可以直接使用。
- 简单类型集合排序参考:java中封装类型和字符串类型List集合排序方法汇总
1、单条件排序
1.1 使用java.util.Collections工具类
使用java.util.Collections工具类对自定义类型的集合进行排序,需要满足以下条件:
- list集合中元素的数据类型是一个java对象;
- 该java对象必须实现Comparable类,重写compareTo方法;
- 注意:使用此方式,集合中不能包含null。
需要排序对象的自定义类:
public class Student implements Comparable<Student> {
public Student(String name, int age){
this.name = name;
this.age = age;
}
private String name;
private int age;
// 省略getter和setter方法
@Override
public int compareTo(Student stu) {
return getAge() - stu.getAge();
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
其中 compareTo 方法用于指示当前元素与其他元素的比较规则,一般都是以 a - b 的形式返回int类型,表示排序规则为从 a 到 b 排序,其逻辑理解就是:如果compareTo方法返回值小于0,则当前元素往前放,大于0,则往后放。
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 集合(自定义类型)排序
*/
public class ListSort {
@Test
public void selfListSort() {
Student stu1=new Student("小张",1);
Student stu2=new Student("小王",2);
Student stu3=new Student("小李",3);
List<Student> list=new ArrayList<>();
list.add(stu2);
list.add(stu1);
list.add(stu3);
// list.add(null); // 放开注释后,运行时报空指针异常
System.out.println("排序前:");
System.out.println(list);
System.out.println("排序后升序:");
// Collections工具类,升序排列
Collections.sort(list);
System.out.println(list);
System.out.println("排序后降序:");
// Collections工具类,降序排列
Collections.reverse(list);
System.out.println(list);
System.out.println("排序后升序:");
// Collections工具类,自然排序为升序排列
Collections.sort(list, Comparator.naturalOrder());
System.out.println(list);
System.out.println("排序后降序:");
// Collections工具类,扭转自然排序为降序排列
Collections.sort(list, Comparator.reverseOrder());
System.out.println(list);
}
}
!
1.2 List对象的sort方法
先看一下List对象的sort方法源码,可以看到参数为Comparator对象。
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
对于自然排序,一个类需要实现Comparable并定义compareTo方法。一个对象的集合根据compareTo方法以自然排序进行排序。像Integer、String和Date这样的Java类实现了Comparable接口并覆盖了其compareTo方法,它们以词表顺序(lexicographic-order)进行排序。为了扭转自然排序,我们可以使用Comparator.reverseOrder方法。
@Test
public void listSort(){
// 使用上例1中的Student类
Student stu1=new Student("小张",1);
Student stu2=new Student("小王",2);
Student stu3=new Student("小李",3);
List<Student> list=new ArrayList<>();
list.add(stu2);
list.add(stu1);
list.add(stu3);
// list.add(null); // 放开注释后,运行时报空指针异常
System.out.println("排序前:");
System.out.println(list);
// 升序
list.sort(Comparator.naturalOrder());
System.out.println("排序后升序:"+list);
// 降序,为了扭转自然排序,我们可以使用Comparator.reverseOrder方法。
list.sort(Comparator.reverseOrder());
System.out.println("排序后降序:"+list);
}
1.3 使用Comparator自定义排序
本例中使用的 Comparator 和 上例中的 Comparable 接口的区别:
-
Comparator 可以看成是外部比较器,因为它是先有list集合,然后再对list集合用比较器去排序;
-
Comparable 可以看成是内部比较器,因为它是直接在java对象实现类添加了比较器,因此是先有比较器,然后再对list集合用比较器去排序;
从上面两点可以推测出 Comparable 的排序算法的效率应该是比 Comparator 要高效的。Comparator 使用了匿名内部类来构建了一个Comparator比较器对象,从而实现排序。
-
优点:不需要在创建java对象时,实现 Comparable 接口。
-
缺点:效率比 Comparable 要低一些。
需要排序对象的自定义类:
public class Student {
public Student(String name, int age){
this.name = name;
this.age = age;
}
private String name;
private int age;
// 省略getter和setter方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
请注意:该Student类与之前的区别,没有实现Comparable接口。
@Test
public void listComparatorSort() {
Student stu1=new Student("小张",1);
Student stu2=new Student("小王",2);
Student stu3=new Student("小李",3);
List<Student> list=new ArrayList<>();
list.add(stu2);
list.add(stu1);
list.add(stu3);
// list.add(null); // 放开注释后,运行时报空指针异常
System.out.println("排序前:");
System.out.println(list);
// 升序排列
list.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
System.out.println("排序后升序:"+list);
// 降序排列
list.sort(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getAge() - o1.getAge();
}
});
System.out.println("排序后降序:"+list);
}
使用 JAVA8 的 lambda 表达式上面Compartor比较器的代码可以简写成以下代码:
// 升序
Collections.sort(list, (stu11, stu21) -> stu11.getAge() - stu21.getAge());
System.out.println(list);
// 降序
Collections.sort(list, (stu11, stu21) -> stu21.getAge() - stu11.getAge());
System.out.println(list);
再进一步简化成如下代码:
// 升序
Collections.sort(list, Comparator.comparingInt(Student::getAge));
System.out.println(list);
// 降序
Collections.sort(list, Comparator.comparingInt(Student::getAge).reversed());
System.out.println(list);
// 升序
Collections.sort(list, Comparator.comparing(Student::getAge));
System.out.println(list);
// 降序
Collections.sort(list, Comparator.comparing(Student::getAge).reversed());
System.out.println(list);
通过查询 comparing开头的方法,可以看见:
经过我的测试发现comparing兼容了下面三种基于整型数据的方法:comparingInt、comparingDouble、comparingLong。显然用comparing很省事,但是一般兼容性越高,效率也就越低,可以推测出comparing方法内部肯定是有一重判断了参数的数据类型的逻辑,这会降低代码执行效率;因此,如果是整型数据的话,建议使用上面三种与数据类型对应的方法,而如果是字符串的话,就使用comparing。
1.4 Stream结合Lambda方法排序
使用java8新特性,Stream结合Lambda表达式对集合进行排序(该排序算法效率高)。
- 有关Lambda可参考:Lambda详解
- 有关Stream可参考:java8新特性Stream流操作详解及实战
- 值得注意的是sorted只是创建一个流对象排序的视图,而不会改变原集合中元素的顺序。也就是说使用sorted方法原有集合的顺序实际上是没有发生变化的。
@Test
public void streamLambdaSort(){
// 使用上例3中的Student类
Student stu1=new Student("小张",1);
Student stu2=new Student("小王",2);
Student stu3=new Student("小李",3);
List<Student> list=new ArrayList<>();
list.add(stu2);
list.add(stu1);
list.add(stu3);
// list.add(null); // 放开注释后,运行时报空指针异常
System.out.println("排序前:");
System.out.println(list);
// 使用Lambda表达式对2个对象进行比较,并输出排序结果,升序
list.stream().sorted((a, b) -> a.getAge() - b.getAge()).forEach(System.out::println);
// 此处的list还是原始未排序集合
System.out.println(list);
// 使用Lambda表达式对2个对象进行比较,并输出排序结果,降序
list.stream().sorted((a, b) -> b.getAge() - a.getAge()).forEach(System.out::println);
// 此处的list还是原始未排序集合
System.out.println(list);
// 升序
list = list.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList());
System.out.println(list);
// 降序
list = list.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList());
System.out.println(list);
}
1.5 Stream结合Comparator方法排序
naturalOrder是比较器功能接口的静态方法。Java 8中引入的Comparator.naturalOrder方法返回一个比较器,该比较器以自然顺序比较可比较对象。为了扭转自然排序,我们可以使用Comparator.reverseOrder方法。
- 值得注意的是sorted只是创建一个流对象排序的视图,而不会改变原集合中元素的顺序。也就是说使用sorted方法原有集合的顺序实际上是没有发生变化的。
@Test
public void streamComparatorSort(){
// 使用例1中的Student类(实现了Comparable接口)
Student stu1=new Student("小张",1);
Student stu2=new Student("小王",2);
Student stu3=new Student("小李",3);
List<Student> list=new ArrayList<>();
list.add(stu2);
list.add(stu1);
list.add(stu3);
// list.add(null); // 放开注释后,运行时报空指针异常
System.out.println("排序前:");
System.out.println(list);
// java.util.Comparator.naturalOrder() 自然排序,并输出排序结果,升序
list.stream().sorted(Comparator.naturalOrder()).forEach(System.out::println);
// 此处的list还是原始未排序集合
System.out.println(list);
// java.util.Comparator.reverseOrder() 扭转自然排序,并输出排序结果,降序
list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);
// 此处的list还是原始未排序集合
System.out.println(list);
}
1.6 集合中存在null的排序方式
这里用到google的guava包,需要先引入以下pom。
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>compile</scope>
</dependency>
</dependencies>
测试代码:
@Test
public void hasNullsort() {
// org.assertj.core.util.Lists;
List<Student> list = Lists.newArrayList();
Student stu1=new Student("小张",1);
Student stu2=new Student("小王",2);
Student stu3=new Student("小李",3);
list.add(stu2);
list.add(stu1);
list.add(stu3);
list.add(null);
System.out.println("排序前:");
System.out.println(list);
// 输出[Student{name='小王', age=2}, Student{name='小张', age=1}, Student{name='小李', age=3}, null]
// com.google.common.collect.Ordering; 需要引入
Ordering<Comparable> natural = Ordering.natural();
Collections.sort(list, natural.nullsLast());
System.out.println("list = " + list);
// 输出list = [Student{name='小张', age=1}, Student{name='小王', age=2}, Student{name='小李', age=3}, null]
Collections.sort(list, natural.nullsFirst());
System.out.println("list = " + list);
// 输出list = [null, Student{name='小张', age=1}, Student{name='小王', age=2}, Student{name='小李', age=3}]
list.removeIf(e -> Objects.isNull(e));
Collections.sort(list);
System.out.println("list = " + list);
// 输出list = [Student{name='小张', age=1}, Student{name='小王', age=2}, Student{name='小李', age=3}]
}
2、多条件排序
Student实体类:
public class StudentMul{
public StudentMul(String name, int age, double grade, int tall){
this.name = name;
this.age = age;
this.grade = grade;
this.tall = tall;
}
private String name;
private int age;
private double grade;
private int tall;
// 省略getter和setter方法
@Override
public String toString() {
return "StudentMul{" +
"name='" + name + '\'' +
", age=" + age +
", grade=" + grade +
", tall=" + tall +
'}';
}
}
排序测试:
@Test
public void multSort(){
StudentMul stu1 = new StudentMul("小米", 20, 95.0, 175);
StudentMul stu2 = new StudentMul("小王", 20, 90.5, 175);
StudentMul stu3 = new StudentMul("小明", 20, 90.0, 180);
List<StudentMul> list = new ArrayList<>();
list.add(stu2);
list.add(stu1);
list.add(stu3);
System.out.println("排序前:");
System.out.println(list);
System.out.println("排序后:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getTall));
System.out.println(list);
System.out.println("倒序后:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getTall).reversed());
System.out.println(list);
System.out.println("1.按年龄升序、分数升序、身高升序排序:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getAge).thenComparingDouble(StudentMul::getGrade).thenComparingInt(StudentMul::getTall));
System.out.println(list);
System.out.println("2.按年龄升序、分数升序、身高降序序排序:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getAge).thenComparingDouble(StudentMul::getGrade).thenComparingInt(StudentMul::getTall).reversed());
System.out.println(list);
System.out.println("3.按年龄升序、分数降序、身高升序排序:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getAge).thenComparingDouble(StudentMul::getGrade).reversed().thenComparingInt(StudentMul::getTall));
System.out.println(list);
System.out.println("4.按年龄升序、分数降序、身高降序序排序:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getAge).thenComparingDouble(StudentMul::getGrade).reversed().thenComparingInt(StudentMul::getTall).reversed());
System.out.println(list);
System.out.println("5.按年龄升序、身高升序、分数升序排序:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getAge).thenComparingInt(StudentMul::getTall).thenComparingDouble(StudentMul::getGrade));
System.out.println(list);
System.out.println("6.按年龄升序、身高升序、分数降序序排序:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getAge).thenComparingInt(StudentMul::getTall).thenComparingDouble(StudentMul::getGrade).reversed());
System.out.println(list);
System.out.println("7.按年龄升序、身高降序、分数升序排序:");
Collections.sort(list, Comparator.comparingInt(StudentMul::getAge).thenComparingInt(StudentMul::getTall).reversed().thenComparingDouble(StudentMul::getGrade));
System.out.println(list);
System.out.println("8.按年龄升序、身高降序、分数降序序排序:");
Collections.sort(list, Comparator.comparing(StudentMul::getAge).thenComparing(StudentMul::getGrade).reversed().thenComparing(StudentMul::getTall).reversed());
System.out.println(list);
}
3、JSONObject集合排序
很多时候为了方便,我们会用到JSONObject或者Map对象去接收数据库返回的结果集,排序时有些地方的处理不太一样。
3.1 JSONObject集合的单条件升序(默认)排序
大多数情况下,我们需要排序的时候,都是单条件排序,所以这是最基本的排序方法,比较简单。
@Test
public void JSONObjectSortAsc(){
List<JSONObject> list = new ArrayList<>();
JSONObject jsonObject1 = new JSONObject();
JSONObject jsonObject2 = new JSONObject();
JSONObject jsonObject3 = new JSONObject();
jsonObject1.put("name", "小米");
jsonObject1.put("age", 20);
jsonObject1.put("grade", 95.0);
jsonObject1.put("tail", 175);
jsonObject2.put("name", "小王");
jsonObject2.put("age", 20);
jsonObject2.put("grade", 90.5);
jsonObject2.put("tail", 175);
jsonObject3.put("name", "小明");
jsonObject3.put("age", 20);
jsonObject3.put("grade", 90.0);
jsonObject3.put("tail", 180);
list.add(jsonObject1);
list.add(jsonObject2);
list.add(jsonObject3);
System.out.println("排序前:");
System.out.println(list);
System.out.println("按成绩升序排序后:");
list = list.stream().sorted(Comparator.comparingDouble(o -> o.getDoubleValue("grade"))).collect(Collectors.toList());
System.out.println(list);
}
3.2 JSONObject集合的单条件降序排序
需要注意的是,和上面提到的第三种排序方式(list.stream().sorted())不同,在对集合进行降序排序的时候,无法直接在链式链式调用后面加上【.reversed()】来实现降序了;经过测试发现list.stream().sorted()对于JSONObject这一类非自定义的java对象无法完美是使用链式调用了,原因很可能是因为JSONObject这一类对象没有确定属性的getter/setter方法,所以下面这种写法会在编译的时候报错:
list = list.stream().sorted(Comparator.comparingDouble(o -> o.getDoubleValue("grade")).reversed()).collect(Collectors.toList());
猜测原因应该是因为在链式调用中 getDoubleValue 会被识别为 JSONObject对象中 doubleValue属性对应的 getter方法,而该对象根本没有这个属性,所以就报错了,所以还是那句话链式调用的规则,并不适用于没有确定属性的这一类对象。可以使用下面这种写法来解决降序的问题:
@Test
public void JSONObjectSortDesc(){
List<JSONObject> list = new ArrayList<>();
JSONObject jsonObject1 = new JSONObject();
JSONObject jsonObject2 = new JSONObject();
JSONObject jsonObject3 = new JSONObject();
jsonObject1.put("name", "小米");
jsonObject1.put("age", 20);
jsonObject1.put("grade", 95.0);
jsonObject1.put("tail", 175);
jsonObject2.put("name", "小王");
jsonObject2.put("age", 20);
jsonObject2.put("grade", 90.5);
jsonObject2.put("tail", 175);
jsonObject3.put("name", "小明");
jsonObject3.put("age", 20);
jsonObject3.put("grade", 90.0);
jsonObject3.put("tail", 180);
list.add(jsonObject1);
list.add(jsonObject2);
list.add(jsonObject3);
System.out.println("排序前:");
System.out.println(list);
System.out.println("按成绩升序排序后:");
list = list.stream().sorted(Comparator.comparingDouble(o -> o.getDoubleValue("grade"))).collect(Collectors.toList());
System.out.println(list);
System.out.println("按成绩降序排序后:");
list = list.stream().sorted((o1, o2) -> o2.getDoubleValue("grade") - o1.getDoubleValue("grade") > 0 ? 1 : -1).collect(Collectors.toList());
System.out.println(list);
}
3.3 JSONObject集合的多条件排序
多条件排序是重点,虽然很多时候我们只需要单条件排序;但多条件排序的写法才是重点,因为这种写法是最基础的,也是兼容最强的。
@Test
public void JSONObjectSortDesc(){
List<JSONObject> list = new ArrayList<>();
JSONObject jsonObject1 = new JSONObject();
JSONObject jsonObject2 = new JSONObject();
JSONObject jsonObject3 = new JSONObject();
jsonObject1.put("name", "小米");
jsonObject1.put("age", 20);
jsonObject1.put("grade", 95.0);
jsonObject1.put("tail", 175);
jsonObject2.put("name", "小王");
jsonObject2.put("age", 20);
jsonObject2.put("grade", 90.5);
jsonObject2.put("tail", 175);
jsonObject3.put("name", "小明");
jsonObject3.put("age", 20);
jsonObject3.put("grade", 90.0);
jsonObject3.put("tail", 180);
list.add(jsonObject1);
list.add(jsonObject2);
list.add(jsonObject3);
System.out.println("排序前:");
System.out.println(list);
System.out.println("按成绩升序排序后:");
list = list.stream().sorted(Comparator.comparingDouble(o -> o.getDoubleValue("grade"))).collect(Collectors.toList());
System.out.println(list);
System.out.println("按成绩降序排序后:");
list = list.stream().sorted((o1, o2) -> o2.getDoubleValue("grade") - o1.getDoubleValue("grade") > 0 ? 1 : -1).collect(Collectors.toList());
System.out.println(list);
System.out.println("1.按年龄升序、分数升序、身高升序排序:");
list = list.stream().sorted((o1, o2) -> {
int value = o1.getIntValue("age") - o2.getIntValue("age");
if ( value == 0 ) {
double v = o1.getDoubleValue("grade") - o2.getDoubleValue("grade");
if ( v == 0.0 ) {
return o1.getIntValue("tail") - o2.getIntValue("tail");
}
return v > 0.0 ? 1 : -1;
}
return value;
}).collect(Collectors.toList());
System.out.println(list);
System.out.println("2.按年龄升序、分数降序、身高升序排序:");
list = list.stream().sorted((o1, o2) -> {
int value = o1.getIntValue("age") - o2.getIntValue("age");
if ( value == 0 ) {
double v = o2.getDoubleValue("grade") - o1.getDoubleValue("grade");
if ( v == 0.0 ) {
return o1.getIntValue("tail") - o2.getIntValue("tail");
}
return v > 0.0 ? 1 : -1;
}
return value;
}).collect(Collectors.toList());
System.out.println(list);
System.out.println("3.按年龄升序、身高升序、分数降序排序:");
list = list.stream().sorted((o1, o2) -> {
int value = o1.getIntValue("age") - o2.getIntValue("age");
if ( value == 0 ) {
value = o1.getIntValue("tail") - o2.getIntValue("tail");
if ( value == 0 ) {
return o2.getDoubleValue("grade") - o1.getDoubleValue("grade") > 0.0 ? 1 : -1;
}
return value;
}
return value;
}).collect(Collectors.toList());
System.out.println(list);
}
评论区