侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 297 篇文章
  • 累计创建 134 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

java中引用类型对象List集合排序方法汇总

孔子说JAVA
2022-07-21 / 0 评论 / 1 点赞 / 48 阅读 / 17,091 字 / 正在检测是否收录...

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);
    }
}

image-1658308465178!

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开头的方法,可以看见:

image-1658310373809

经过我的测试发现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);
}
1

评论区