【Java必修课】一图说尽排序,一文细说Sorting(Array、List、Stream的排序)
简说排序
排序是极其常见的使用场景,因为在生活中就有很多这样的实例。国家GDP排名、奥运奖牌排名、明星粉丝排名等,各大排行榜,给人的既是动力,也是压力。
而讲到排序,就会有各种排序算法和相关实现,本文不讲任何排序算法,而只专注于讲使用。通过实例给大家展示,我们可以了解怎样使用既有的工具进行排序。Linux之父说:
Talk is cheap. show me the code!
本文JDK版本为Java 8,但并不代表所介绍到的所有方法只能在JDK1.8上跑,部分方法在之前的版本就已经给出。
如下本次整理的图,记住图中的方法,就能轻松应对大多数场景,赶紧收藏起来吧,哈哈

两个接口
Comparable
先上代码:
package java.lang;
public interface Comparable<T> {
public int compareTo(T o);
}
可以看出这个接口只有一个方法,这个方法只有一个参数,实现了这个接口的类就可以和同类进行比较了。这个方法所实现的,就是比较法则,也是说,它表示如何对两个对象进行比较。
它返回的是一个整数int:
- 返回正数,代表当前对象大于被比较的对象;
- 返回0,代表当前对象等于于被比较的对象;
- 返回负数,代表当前对象小于被比较的对象。
实现了该接口后,我们就可以使用Arrays.sort()和Collections.sort()来进行排序了。
不然对象没有比较法则,程序肯定是不知道如何进行比较排序的。
像我们常用的类String、Integer、Double、Date等,JDK都帮我们实现了Comparable接口,我们可以直接对这类对象进行比较排序。
举个例子,Date Comparable的实现:
public int compareTo(Date anotherDate) {
long thisTime = getMillisOf(this);
long anotherTime = getMillisOf(anotherDate);
return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1));
}
需要注意的是,当我们自己去实现Comparable接口时,一定要注意与equals()方法保持一致。当两个对象是equals的,compare的结果应该是相等的。
Comparator
还是先看代码,看看接口定义吧:
package java.util;
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
它是一个函数式接口,它的compare方法有两个参数,代表进行比较的两个对象。这个接口代表了可以作为某种对象比较的一种法则,或叫一种策略。
它的返回值正负代表意义与Comparable接口的方法一样。
它的使用通常会有三种方式:
- 实现类
- 匿名类
- Lambda表达式
在Java8之后,我们一般用Lambda比较多,也比较灵活优雅。
两个接口的比较
两个接口功能都是用于比较排序,但其实有很大的区别。
- 两者方法参数不同,Comparable只有一个参数,表示被比较的对象,因为它的方法是位于需要比较的类里的,所以只要一个参数就可以了;而Comparator的比较方法则有两个参数,分别表示比较对象和被比较对象。
- Comparable与对象绑定,位于对象内,我们可以称之为内比较器;而Comparator是独立于需要比较的类的,我们可以称为外比较器。
- 当类实现了Comparable方法后,比较法则就确定了,我们称之为自然比较方法,我们无法给它实现多种比较方法;而因为Comparator独立于外,我们可以为同一个类提供多种Comparator的实现,这样来提供多种比较方法/策略,如升序倒序,因此我们也可以将Comparator看成是一种策略模式。
相对于Comparable,Comparator有一定的灵活性,假如一个类并没有实现Comparable接口,并且这个类是无法修改的,我们就要通过提供Comparator来进行比较排序。
Comparator这种模式实现了数据与算法的解耦合,对于维护也是很方便的。
工具类
十分友好的是,JDK为我们提供了工具类,它们的静态方法可以帮助我们直接对数组和List进行排序。
数组排序Arrays
Arrays的sort方法可以对已经实现了Comparable接口的进行排序,同时还可指定排序的范围。
//Arrays.sort对String进行排序
String[] strings = {"de", "dc", "aA", "As", "k", "b"};
Arrays.sort(strings);
assertTrue(Arrays.equals(strings,
new String[]{"As", "aA", "b", "dc", "de", "k"}));
指定范围排序,需要注意的是,index是从0开始算的,包含fromIndex,不包含toIndex:
//Arrays.sort指定范围排序
strings = new String[]{"z", "a", "d", "b"};
Arrays.sort(strings, 0, 3);
assertTrue(Arrays.equals(strings,
new String[]{"a", "d", "z", "b"}));
对于基本类型,一样可以进行排序,并不需要使用封装类:
//Arrays.sort对基本类型排序
int[] nums = {3, 1, 20, 2, 38, 2, 94};
Arrays.sort(nums);
assertTrue(Arrays.equals(nums,
new int[]{1, 2, 2, 3, 20, 38, 94}));
还能多线程进行排序,其实是拆分成多个子数组再进行排序,最终再合并结果。
//Arrays.parallelSort多线程排序
nums = new int[]{3, 1, 20, 2, 38, 2, 94};
Arrays.parallelSort(nums);
assertTrue(Arrays.equals(nums,
new int[]{1, 2, 2, 3, 20, 38, 94}));
对于没有实现Comparable的类也可以使用,但需要提供Comparator来指定比较策略。
本文的没有实现Comparable接口的类Person如下:
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Person {
private String name;
private int age;
}
排序:
//Arrays.sort提供Comparator进行排序
Person[] persons = new Person[]{
new Person("Larry", 18),
new Person("David", 30),
new Person("James", 20),
new Person("Harry", 18)};
Arrays.sort(persons, Comparator.comparingInt(Person::getAge));
assertTrue(Arrays.equals(persons, new Person[]{
new Person("Larry", 18),
new Person("Harry", 18),
new Person("James", 20),
new Person("David", 30)}));
List排序Collections
JDK的Collections工具类提供了排序方法,可以方便使用。
对于实现Comparable的类进行排序:
//Collections.sort对于实现Comparable的类进行排序
List<String> names = asList("Larry", "Harry", "James", "David");
Collections.sort(names);
assertEquals(names, asList("David", "Harry", "James", "Larry"));
提供Comparator进行排序:
//Collections.sort提供Comparator进行排序
List<Person> persons2 = asList(
new Person("Larry", 18),
new Person("David", 30),
new Person("James", 20),
new Person("Harry", 18));
Collections.sort(persons2, Comparator.comparingInt(Person::getAge));
assertEquals(persons2, asList(
new Person("Larry", 18),
new Person("Harry", 18),
new Person("James", 20),
new Person("David", 30)));
反序:只是把List反过来,并不代表一定是按照大小顺序的:
//Collections.reverse反序
names = asList("Larry", "Harry", "James", "David");
Collections.reverse(names);
assertEquals(names, asList("David", "James", "Harry", "Larry"));
成员方法
List排序
List接口有sort(Comparator<? super E> c)方法,可以实现对自身的排序,会影响自身的顺序。
//List.sort排序
names = asList("Larry", "Harry", "James", "David");
names.sort(Comparator.naturalOrder());
assertEquals(names, asList("David", "Harry", "James", "Larry"));
Stream排序
Stream提供了sorted()和sorted(Comparator<? super T> comparator)进行排序,会返回一个新的Stream。
//Stream.sorted排序
names = asList("Larry", "Harry", "James", "David");
List<String> result = names.stream()
.sorted()
.collect(Collectors.toList());
assertEquals(result, asList("David", "Harry", "James", "Larry"));
//Stream.sorted提供Comparator排序
names = asList("Larry", "Harry", "James", "David");
result = names.stream()
.sorted(Comparator.naturalOrder())
.collect(Collectors.toList());
assertEquals(result, asList("David", "Harry", "James", "Larry"));
方便对象排序的Comparator
单字段排序
对类的单字段进行排序很简单,只要提供形如:
- Comparator.comparing(类名::属性getter)
的Comparator就行了。如果需要倒序,就需要:
- Comparator.comparing(类名::属性getter).reversed()
- 或Comparator.comparing(类名::属性getter, Comparator.reverseOrder())。
具体代码使用(为了不破坏List的原有顺序,我们都使用Stream来操作):
//单字段排序-升序
List<Person> personList = asList(
new Person("Larry", 18),
new Person("David", 30),
new Person("David", 3),
new Person("James", 20),
new Person("Harry", 18));
List<Person> personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName))
.collect(Collectors.toList());
assertEquals(personResult, asList(
new Person("David", 30),
new Person("David", 3),
new Person("Harry", 18),
new Person("James", 20),
new Person("Larry", 18)));
//单字段排序-倒序1
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName).reversed())
.collect(Collectors.toList());
assertEquals(personResult, asList(
new Person("Larry", 18),
new Person("James", 20),
new Person("Harry", 18),
new Person("David", 30),
new Person("David", 3)));
//单字段排序-倒序2
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder()))
.collect(Collectors.toList());
assertEquals(personResult, asList(
new Person("Larry", 18),
new Person("James", 20),
new Person("Harry", 18),
new Person("David", 30),
new Person("David", 3)));
多字段排序
多字段其实也很方便,只需要用thenComparing进行连接就可以:
Comparator.comparing(类名::属性一getter).thenComparing(类名::属性二getter)
具体代码使用例子如下:
//多字段排序-1升2升
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge))
.collect(Collectors.toList());
assertEquals(personResult, asList(
new Person("David", 3),
new Person("David", 30),
new Person("Harry", 18),
new Person("James", 20),
new Person("Larry", 18)));
//多字段排序-1升2倒
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName)
.thenComparing(Person::getAge, Comparator.reverseOrder()))
.collect(Collectors.toList());
assertEquals(personResult, asList(
new Person("David", 30),
new Person("David", 3),
new Person("Harry", 18),
new Person("James", 20),
new Person("Larry", 18)));
//多字段排序-1倒2升
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder())
.thenComparing(Person::getAge))
.collect(Collectors.toList());
assertEquals(personResult, asList(
new Person("Larry", 18),
new Person("James", 20),
new Person("Harry", 18),
new Person("David", 3),
new Person("David", 30)));
//多字段排序-1倒2倒
personResult = personList.stream()
.sorted(Comparator.comparing(Person::getName, Comparator.reverseOrder())
.thenComparing(Person::getAge, Comparator.reverseOrder()))
.collect(Collectors.toList());
assertEquals(personResult, asList(
new Person("Larry", 18),
new Person("James", 20),
new Person("Harry", 18),
new Person("David", 30),
new Person("David", 3)));
总结
本文从比较排序相关的两个接口(Comparable和Comparator)讲起,并以代码实例的形式,讲解了Array、List、Stream排序的方法,这应该可以覆盖大部分Java排序的使用场景。
对于其它集合类如Set和Map,一样可以进行排序处理,可以将它们转化为Stream然后再进行排序。
【Java必修课】一图说尽排序,一文细说Sorting(Array、List、Stream的排序)的更多相关文章
- java 集合框架图
Java平台提供了一个全新的集合框架.“集合框架”主要由一组用来操作对象的接口组成.不同接口描述一组不同数据类型. Java 2集合框架图集合接口:6个接口(短虚线表示),表示不同集合类型,是集合框架 ...
- Java实现web在线预览office文档与pdf文档实例
https://yq.aliyun.com/ziliao/1768?spm=5176.8246799.blogcont.24.1PxYoX 摘要: 本文讲的是Java实现web在线预览office文档 ...
- java异常架构图 和几个面试题
1.java异常架构图 粉红色的是受检查的异常(checked exceptions),其必须被 try{}catch语句块所捕获,或者在方法签名里通过throws子句声明.受检查的异常必须在编译时被 ...
- Java常用排序算法+程序员必须掌握的8大排序算法+二分法查找法
Java 常用排序算法/程序员必须掌握的 8大排序算法 本文由网络资料整理转载而来,如有问题,欢迎指正! 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排 ...
- [转]Java 常用排序算法/程序员必须掌握的 8大排序算法
本文转自:http://www.cnblogs.com/qqzy168/archive/2013/08/03/3219201.html 本文由网络资料整理转载而来,如有问题,欢迎指正! 分类: 1)插 ...
- Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- Java常用排序算法+程序员必须掌握的8大排序算法
概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存. 我们这里说说八大排序就是内部排序. 当n较大, ...
- Java 常用排序算法/程序员必须掌握的 8大排序算法
Java 常用排序算法/程序员必须掌握的 8大排序算法 分类: 1)插入排序(直接插入排序.希尔排序) 2)交换排序(冒泡排序.快速排序) 3)选择排序(直接选择排序.堆排序) 4)归并排序 5)分配 ...
- java中的xpath,读取xml文档。
1,入门 XPath即为XML路径语言(XML Path Language),它是一种用来确定XML文档中某部分位置的语言. XPath基于XML的树状结构,提供在数据结构树中找寻节点的能力.起初 X ...
随机推荐
- 【LeetCode】78-子集
题目描述 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集). 说明:解集不能包含重复的子集. 示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [ ...
- 数据库常用SQL语句(一):常用的数据库、表操作及单表查询语句
以MySql数据库为例进行说明 1.数据库操作语句 2.表的操作语句 3.表中的字段操作语句 4.MYSQL支持的完整性约束 数据库管理系统提供了一致机制来检查数据库表中的数据是否满足规定的条件,以保 ...
- Java EE—最轻量级的企业框架?
确保高效发展进程的建议 很久以前,J2EE,特别是应用程序服务器被认为过于臃肿和"重量级".对于开发人员来说,使用此技术开发应用程序会非常繁琐且令人沮丧.但是,由于 J2EE 框架 ...
- Failed to execute goal on project e3-manager: Could not resolve dependencies for project cn.e3mall:e3-manager:pom:0.0.1-SNAPSHOT: Could not find artifact cn.e3mall:e3-parent:jar:0.0.1-SNAPSHOT
新建好工程后一定要记得从底层开始clean和install 在启动新建的工程时到最后一步出现了这个问题: Failed to execute goal on project e3-manager-we ...
- Python 基础 3 - 元组
元组与列表区别 Python 元组与列表类似,不同之处在于列表可以修改,元组不可以修改 元组用小括号 () 定义,列表用方括号 [] 定义 元组不可修改,列表可修改 元组创建 只需要在小括号 () 内 ...
- CentOS7 使用tree命令不显示中文
1.你如果使用tree命令看见的情况是这样 [lk@localhost ~]$ tree . ├── perl5 ├── Python-.tgz ├── \\\\\\ ├── \\\\\\ ├── \ ...
- 用vetr.x写一个HTTP接口适配器, 对接各种形式接口
用vetr.x写一个HTTP接口适配器, 对接各种形式接口 项目地址:https://github.com/hjx601496320/transmit 业务说明 在日常开发工作中,我们经常会遇到要和各 ...
- FlutterGo 后端知识点提炼:midway+Typescript+mysql(sequelize)
前言 关于 FlutterGo 或许不用太多介绍了. 如果有第一次听说的小伙伴,可以移步FlutterGo官网查看下简单介绍. FlutterGo 在这次迭代中有了不少的更新,笔者在此次的更新中,负责 ...
- Swift从入门到精通第七篇 - 扩展 初识
扩展(学习笔记) 环境Xcode 11.0 beta4 swift 5.1 扩展 为类.结构体.枚举.协议添加新功能,同OC的分类很像,但扩展没有名字 扩展可以添加计算实例属性和计算类型属性(不能添加 ...
- 一起看期待已久的.NET Core 3.0新的单文件部署特性,记在昨日VS2019更新后
VS2019又又又迎来一次新的更新,这次的重点在.NET Core, 妥妥的更新好,默默地反选2.2,一切都在意料之中. 这次我们来看VS2019的新特性单文件部署: https://www.talk ...