在某些特殊的场景下,我们需要在 Java 程序中对 List 集合进行排序操作。比如从第三方接口中获取所有用户的列表,但列表默认是以用户编号从小到大进行排序的,而我们的系统需要按照用户的年龄从大到小进行排序,这个时候,我们就需要对 List 集合进行自定义排序操作了。

List 排序的常见方法有以下 3 种:

  1. 使用 Comparable 进行排序;
  2. 使用 Comparator 进行排序;
  3. 如果是 JDK 8 以上的环境,也可以使用 Stream 流进行排序。

下面我们分别来看各种排序方法的具体实现。

1.使用 Comparable 排序

按照本文设计的场景,我们需要创建一个包含了用户列表的 List 集合,并按用户的年龄从大到小进行排序,具体实现代码如下:

public class ListSortExample {
public static void main(String[] args) {
// 创建并初始化 List
List<Person> list = new ArrayList<Person>() {{
add(new Person(1, 30, "北京"));
add(new Person(2, 20, "西安"));
add(new Person(3, 40, "上海"));
}};
// 使用 Comparable 自定的规则进行排序
Collections.sort(list);
// 打印 list 集合
list.forEach(p -> {
System.out.println(p);
});
}
} // 以下 set/get/toString 使用的是 lombok 的注解
@Getter
@Setter
@ToString
class Person implements Comparable<Person> {
private int id;
private int age;
private String name; public Person(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
} @Override
public int compareTo(Person p) {
return p.getAge() - this.getAge();
}
}

以上代码的执行结果,如下图所示:



本方法的核心代码如下:

2.使用 Comparator 排序

Comparable 是类内部的比较方法,而 Comparator 是排序类外部的比较器。使用 Comparator 比较器,无需修改原 Person 类,只需要扩充一个 Person 类的比较器就行了,Comparator 的实现方法有以下两种:

  • 新建 Comparator 比较器;
  • 使用 Comparator 匿名类比较器。

其中,第二种实现方法要更简洁一些,我们通过下面的具体代码,来观察一下二者的区别。

2.1 新建 Comparator 比较器

public class ListSortExample2 {
public static void main(String[] args) {
// 创建并初始化 List
List<Person> list = new ArrayList<Person>() {{
add(new Person(1, 30, "北京"));
add(new Person(2, 20, "西安"));
add(new Person(3, 40, "上海"));
}};
// 使用 Comparator 比较器排序
Collections.sort(list, new PersonComparator());
// 打印 list 集合
list.forEach(p -> {
System.out.println(p);
});
}
}
/**
* 新建 Person 比较器
*/
class PersonComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p2.getAge() - p1.getAge();
}
}
@Getter
@Setter
@ToString
class Person {
private int id;
private int age;
private String name; public Person(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
}

以上代码的执行结果,如下图所示:



本方法的核心实现代码如下:

2.2 匿名类比较器

比较器 Comparator 可以使用更简洁的匿名类的方式,来实现排序功能,具体实现代码如下:

public class ListSortExample2 {
public static void main(String[] args) {
// 创建并初始化 List
List<Person> list = new ArrayList<Person>() {{
add(new Person(1, 30, "北京"));
add(new Person(2, 20, "西安"));
add(new Person(3, 40, "上海"));
}};
// 使用匿名比较器排序
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p2.getAge() - p1.getAge();
}
});
// 打印 list 集合
list.forEach(p -> {
System.out.println(p);
});
}
}
@Getter
@Setter
@ToString
class Person {
private int id;
private int age;
private String name;
public Person(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
}

以上代码的执行结果,如下图所示:

3.使用 Stream 流排序

在 JDK 8 之后可以使用更加简单的方法 Stream 流来实现排序功能,它的实现只需要一行代码,具体实现如下:

public class ListSortExample3 {
public static void main(String[] args) {
// 创建并初始化 List
List<Person> list = new ArrayList<Person>() {{
add(new Person(1, 30, "北京"));
add(new Person(2, 20, "西安"));
add(new Person(3, 40, "上海"));
}};
// 使用 Stream 排序
list = list.stream().sorted(Comparator.comparing(Person::getAge).reversed())
.collect(Collectors.toList());
// 打印 list 集合
list.forEach(p -> {
System.out.println(p);
});
}
@Getter
@Setter
@ToString
static class Person {
private int id;
private int age;
private String name;
public Person(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
}
}

其中 reversed() 表示倒序的意思,如果不使用此方法则是正序。

以上代码的执行结果,如下图所示:

扩展:排序字段为 null

使用 Stream 进行排序时,如果排序的字段出现 null 值就会导致异常发生,具体示例如下:

public class ListSortExample4 {
public static void main(String[] args) {
// 创建并初始化 List
List<Person> list = new ArrayList<Person>() {{
add(new Person(30, "北京"));
add(new Person(10, "西安"));
add(new Person(40, "上海"));
add(new Person(null, "上海")); // 年龄为 null 值
}};
// 按照[年龄]正序,但年龄中有一个 null 值
list = list.stream().sorted(Comparator.comparing(Person::getAge))
.collect(Collectors.toList());
// 打印 list 集合
list.forEach(p -> {
System.out.println(p);
});
}
}
@Getter
@Setter
@ToString
class Person {
private Integer age;
private String name; public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
}

以上代码的执行结果,如下图所示:



想要解决上述问题,需要给 Comparator.comparing 传递第二个参数:Comparator.nullsXXX,如下代码所示:

public class ListSortExample4 {
public static void main(String[] args) {
// 创建并初始化 List
List<Person> list = new ArrayList<Person>() {{
add(new Person(30, "北京"));
add(new Person(10, "西安"));
add(new Person(40, "上海"));
add(new Person(null, "上海"));
}};
// 按照[年龄]正序,但年龄中有一个 null 值
list = list.stream().sorted(Comparator.comparing(Person::getAge,
Comparator.nullsFirst(Integer::compareTo)))
.collect(Collectors.toList());
// 打印 list 集合
list.forEach(p -> {
System.out.println(p);
});
}
}
@Getter
@Setter
@ToString
class Person {
private Integer age;
private String name; public Person(Integer age, String name) {
this.age = age;
this.name = name;
}
}

Comparator.nullsFirst 表示将排序字段中的 null 值放到集合最前面,如果想要将 null 值放到集合最后面可以使用 Comparator.nullsLast。

以上代码的执行结果,如下图所示:

总结

本文介绍了 3 种 List 排序的方法,前两种方法常用于 JDK 8 之前的版本,其中比较器 Comparator 有两种实现的写法,而在 JDK 8 之后的版本,就可以使用 Comparator.comparing 实现排序了,如果排序字段中可能出现 null 值,要使用 Comparator.nullsXXX 进行排序处理(否则会报错)。

卒然临之而不惊,无故加之而不怒。享受平凡生活中的喜悦,终身成长者。

博主:80 后程序员。爱好:读书、写作和慢跑。

公众号:Java面试真题解析

Java中List排序的3种方法的更多相关文章

  1. Java中创建数组的几种方法

    Java中创建数组的几种方法 public static void main(String[] args) { //创建数组的第一种方法 int[] arr=new int[6]; int intVa ...

  2. 谈谈java中遍历Map的几种方法

    java中的map遍历有多种方法,从最早的Iterator,到java5支持的foreach,再到java8 Lambda,让我们一起来看下具体的用法以及各自的优缺点 先初始化一个map public ...

  3. Java中遍历map的四种方法 - 转载

    在Java中如何遍历Map对象 How to Iterate Over a Map in Java 在java中遍历Map有不少的方法.我们看一下最常用的方法及其优缺点. 既然java中的所有map都 ...

  4. Java 中 List 分片的 5 种方法!

    前些天在实现 MyBatis 批量插入时遇到了一个问题,当批量插入的数据量比较大时,会导致程序执行报错,如下图所示: 原因是 MySQL 只能执行一定长度的 SQL 语句,但当插入的数据量较多时,会生 ...

  5. JAVA中创建线程的三种方法及比较

    JAVA中创建线程的方式有三种,各有优缺点,具体如下: 一.继承Thread类来创建线程 1.创建一个任务类,继承Thread线程类,因为Thread类已经实现了Runnable接口,然后重写run( ...

  6. java中遍历map的几种方法介绍

          喜欢用Java写程序的朋友都知道,我们常用的一种数据结构map中存储的是键值对,我们一般存储的方式是: map.put(key, value); 而提取相应键的值用的方法是: map.ge ...

  7. 干货 | Java中获取类名的3种方法!

    获取类名的方法 Java 中获取类名的方式主要有以下三种. getName() 返回的是虚拟机里面的class的类名表现形式. getCanonicalName() 返回的是更容易理解的类名表示. g ...

  8. Java中终止线程的三种方法

    终止线程一般建议采用的方法是让线程自行结束,进入Dead(死亡)状态,就是执行完run()方法.即如果想要停止一个线程的执行,就要提供某种方式让线程能够自动结束run()方法的执行.比如设置一个标志来 ...

  9. java中创建线程的几种方法及区别

    1,实现Runnable接口创建线程 特点: A:将代码和数据分开,形成清晰的模型 B:线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法 C:有利于保持程序风格的一致性 2,继承Th ...

随机推荐

  1. [atARC078F]Mole and Abandoned Mine

    注意到最终图的样子可以看作一条从1到$n$的路径,以及删去这条路径上的边后,路径上的每一个点所对应的一个连通块 考虑dp,令$f_{S,i}$表示当前1到$n$路径上的最后一个点以及之前点(包括$i$ ...

  2. [cf1184E]Daleks' Invasion

    先求出任意一棵最小生成树,然后对边分类讨论1.非树边,答案即最小生成树的环上的最长边2.树边,反过来考虑,相当于对于每一个点对那条路经打上标记,取min对于1直接用倍增维护即可,对于2可以用树链剖分/ ...

  3. 数字逻辑实践2->Verilog编写规范

    来源:数字逻辑与Verilog设计实验课讲解,个人做的笔记与整理. 00 规范的重要性 良好的编程风格有利于减少消耗的硬件资源,提高设计的工作频率 . 提高系统的可移植性和可维护性. 程序的格式化能体 ...

  4. HCNP Routing&Switching之组播技术-组播基础

    组播技术背景 随着internet网络的不断发展,网络中交互的各种数据.语音.视频信息数量突增:新型的在线直播.网络电视.视频会议等应用也在逐渐兴起:这些业务大多符合点到多点的模式,对信息安全性.传播 ...

  5. nginx安装与配置3-反向代理两台

    1.nginx 反向代理 两台tomcat 2.8080.8081 启动tomcat 记住每个tomcat都有两个端口不要出现tomcat端口占用情况 3.启动项目访问,不报错可以访问 4.在每个to ...

  6. 多线程02.继承Thread类

    package chapter2; /** * 1.使用static虽然完成了当前的功能. * 但是static的生命周期比较长,需要在所有线程执行完成后才会结束. * 还有号没有按照顺序进行打印 * ...

  7. Python基础笔记3

    高级特性 代码不是越多越好,而是越少越好.代码不是越复杂越好,而是越简单越好.代码越少,开发效率越高. 1.切片 切片(Slice)操作符,取一个list或tuple的部分元素非常常见. 列表 L = ...

  8. shell批量创建用户

    #!/bin/bash cat << EOF ************************************************************ 批量添加用户并随机生 ...

  9. Go 性能提升tips--边界检查

    1. 什么是边界检查? 边界检查,英文名 Bounds Check Elimination,简称为 BCE.它是 Go 语言中防止数组.切片越界而导致内存不安全的检查手段.如果检查下标已经越界了,就会 ...

  10. javaWeb - 1 — servlet — 更新完毕

    1.先来聊一些javaWeb相关的知识 简单了解一下:web的发展史 1).web就是网页的意思嘛 2).web的分类 (1).静态web 使用HTML.CSS技术,主要包括图片和文本 优点:简单,只 ...