Java 8 排序的 10 个姿势,太秀了吧!同事直呼看不懂。。

Java技术栈

共 11786字,需浏览 24分钟

 · 2022-06-20

点击关注公众号,Java干货及时送达

大家好,我是栈长。

本公众号(Java技术栈)平时会发不少干货,值得大家的关注,都是栈长多年积累的经验精华,希望对大家有帮助,大家可以置顶下公众号,别错过任何精彩内容!

不废话了,不信你继续往下看,=-=

今天栈长就分享 Java 8 进行排序的 10 个姿势,原来还有这么多排序技巧,其实就是把 Java 8 中的 Lambda、Stream、方法引用等知识点串起来,栈长的同事直呼还看不懂。。

传统排序

现在有一个 List 集合:

public static List<User> LIST = new ArrayList() {
    {
        add(new User("Lisa", 23));
        add(new User("Tom", 11));
        add(new User("John", 16));
        add(new User("Jessie", 26));
        add(new User("Tony", 26));
        add(new User("Messy", 26));
        add(new User("Bob", 19));
        add(new User("Yoga", 65));
    }
};

jdk8 之前的排序:

/**
 * jdk8 之前的排序
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortPreJdk8() {
    System.out.println("=====jdk8 之前的排序=====");
    List<User> list = new ArrayList<>(LIST);

    Collections.sort(list, new Comparator<User>() {
        @Override
        public int compare(User u1, User u2) {
            return u1.getAge().compareTo(u2.getAge());
        }
    });

    for (User user : list) {
        System.out.println(user);
    }
    System.out.println();
}

在 Java 8 出来之前,排序基本上要这么写,可是 Java 8 都出来这么多年了,你还在这么排序那就太 Low 了!

Java 8 中的排序

对 Java 8 新增的知识点这篇不再详述,还不会用的可以关注公众号:Java技术栈,在后台回复:java,Java 8+ 系列教程我都写了一堆了。

本篇就直接上干货,看我怎么用 Java 8 排序!

1、Lambda 排序(带参数类型)

Java 8 中的 List 接口新增了一个 sort 默认方法:

接收 Comparator 接口参数,这个接口在 Java 8 中被修饰为函数式接口:

然后我们就可以把 Comparator 接口参数改成了用 Lambda 表达式的形式,用 Lambda 表达式干掉了匿名内部类,让代码更简洁。

使用示例如下:

/**
 * jdk8 lambda 排序,带参数类型
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8Lambda1() {
    System.out.println("=====jdk8 lambda 排序,带参数类型=====");
    List<User> list = new ArrayList<>(LIST);

    list.sort((User u1, User u2) -> u1.getAge().compareTo(u2.getAge()));

    list.forEach(System.out::println);
    System.out.println();
}

2、Lambda 排序(不带参数类型)

Lambda 表达式是可以不用带参数类型的,如下示例:

/**
 * jdk8 lambda 排序,不带参数类型
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8Lambda2() {
    System.out.println("=====jdk8 lambda 排序,不带参数类型=====");
    List<User> list = new ArrayList<>(LIST);

    list.sort((u1, u2) -> u1.getAge().compareTo(u2.getAge()));

    list.forEach(System.out::println);
    System.out.println();
}

代码中的 u1, u2 并没有用 User 类修饰,它会自动推断为 User 类型,因为集合本身就是一个 User 泛型。

3、静态方法引用排序

除了 Lambda 表达式,还可以用类的静态方法引用:

/**
 * jdk8 静态方法引用排序
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8StaticMethodRef() {
    System.out.println("=====jdk8 静态方法引用排序=====");
    List<User> list = new ArrayList<>(LIST);

    list.sort(User::compareAge);

    list.forEach(System.out::println);
    System.out.println();
}

使用方法引用之后代码是不是更简洁了?

4、实例方法引用排序

不仅可以用类的静态方法,还可以用类的实例普通方法引用:

/**
 * jdk8 实例方法引用排序
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8InstanceMethodRef() {
    System.out.println("=====jdk8 实例方法引用排序=====");
    List<User> list = new ArrayList<>(LIST);

    list.sort(User.getInstance()::compare);

    list.forEach(System.out::println);
    System.out.println();
}

这个 getInstance 在这里实际上是一个单例,但和单例无关,任何类的实例都可以。

另外,这些知识点我也整理到了小程序,都是面试常考的,大家可以在Java面试库小程序在线刷题。

5、Comparator 工具类排序(升序)

Java 8 在 Comparator 接口中新增了 comparing 方法:

这个工具方法需要提供一个函数式接口参数,也就是要比较的哪个字段,最后还是返回 Comparator 接口实例。

使用示例如下:

/**
 * jdk8 升序排序,Comparator 提供的静态方法
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8ComparatorAsc() {
    System.out.println("=====jdk8 升序排序=====");
    List<User> list = new ArrayList<>(LIST);

    list.sort(Comparator.comparing(User::getAge));
    
//  list.sort(Comparator.comparing((user) -> user.getAge()));

    list.forEach(System.out::println);
    System.out.println();
}

既然是函数式接口,所以又可以用 Lambda、方法引用形式作为参数传入。

本文所有完整示例源代码已经上传:

https://github.com/javastacks/javastack

6、Comparator 工具类排序(降序)

还可以使用 Comparator.reversed/ reversedOrder 方法进行降序:

/**
 * jdk8 Comparator 工具类排序(降序)
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8ComparatorDesc() {
    System.out.println("=====jdk8 降序降序=====");
    List<User> list = new ArrayList<>(LIST);

    list.sort(Comparator.comparing(User::getAge).reversed());

    list.forEach(System.out::println);
    System.out.println();
}

Comparator.reversed 和 reversedOrder 的区别在于,reversedOrder 适用于基本数据类型的集合的自然排序,而 reversed 是对 Comparator 自身的封装,适用于对象的自定义排序。

点击关注公众号,Java干货及时送达

7、组合排序

如果要先按用户的年龄排序,年龄相同的再按姓名排序,可以使用 Comparator 接口中的 thenComparing 默认方法:

private static void sortGroupWithJdk8() {
    System.out.println("=====jdk8 组合排序=====");
    List<User> list = new ArrayList<>(LIST);

    list.sort(Comparator.comparing(User::getAge).thenComparing(User::getName));

    list.forEach(System.out::println);
    System.out.println();
}

输出结果:

=====jdk8 组合排序=====
11: Tom
16: John
19: Bob
23: Lisa
26: Jessie
26: Messy
26: Tony
65: Yoga

注意年龄 26 岁的人又按姓名按自然顺序排序了。

8、Stream 排序

还可以把 List 集合转换为 Stream,然后使用其 sorted 方法:

sorted 方法也是接收 Comparator 接口参数,所以我们也可以使用 Lambda、方法引用、Comparator 接口自身提供的工具方法对其调用:

/**
 * jdk8 Stream 排序
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8Stream() {
    System.out.println("=====jdk8 Stream 排序=====");
    List<User> list = new ArrayList<>(LIST);

    list = list.stream().sorted(User::compareAge).collect(Collectors.toList());

//  list = list.stream().sorted((u1, u2) -> u1.getAge().compareTo(u2.getAge())).collect(Collectors.toList());

//  list = list.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
    

    list.forEach(System.out::println);
    System.out.println();
}

所以使用 Stream 也可以扩展多种排序方法,见注释部分,这里就不展开了。另外,Stream 系列我之前写过一个专题了,这里不再展开,不懂的关注公众号Java技术栈,然后在公众号 Java 教程菜单中阅读。

9、并行 Stream 排序

有 Stream 排序,那就并行 Stream(parallelStream)排序:

/**
 * jdk8 并行 Stream 排序
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithJdk8parallelStream() {
    System.out.println("=====jdk8 Stream 排序=====");
    List<User> list = new ArrayList<>(LIST);

    list = list.parallelStream().sorted(User::compareAge).collect(Collectors.toList());

    list.forEach(System.out::println);
    System.out.println();
}

10、Collections 排序

既然在 Java 8 中,Comparator 接口被定义成了函数式接口,那么我们传统的 Collections 工具类就可以改变了,我们也可以使用 Lambda、方法引用、Comparator 接口自身提供的工具方法对其调用:

/**
 * jdk8 Collections 排序
 * @author: 栈长
 * @from: 公众号Java技术栈
 */
private static void sortWithCollections() {
    System.out.println("=====jdk8 Collections 排序=====");
    List<User> list = new ArrayList<>(LIST);

    Collections.sort(list, User::compareAge);
    
//  Collections.sort(list, (u1, u2) -> u1.getAge().compareTo(u2.getAge()));
//  Collections.sort(list, Comparator.comparing(User::getAge));    

    list.forEach(System.out::println);
    System.out.println();
}

你学废了吗?

总结

本文栈长列举了 Java 8 中的 10 种排序方法,其实就是 10 个案例,还可以扩展更多,只是给大家个参考,总体来说,其实可以分为 3 大类:

  • List 接口中的 sort 方法
  • Stream 接口中的 sorted 方法
  • Collections.sort 工具类方法

这三个方法都可以接收 Comparator 接口作为参数,并且 Comparator 接口在 Java 8 中被定义成了函数式接口,所以我们可以用 Lambda 表达式、方法引用、Comparator 自身工具类等不同的参数形式传入,可谓是太秀了。

这些方法性能如何呢?

栈长写了一个小例子,1 万数据的集合,使用静态方法引用进行测试:

long start = System.currentTimeMillis();
List<User> list1 = new ArrayList<>(list);
list1.sort(User::compareAge);
System.out.println("List.sort: " + (System.currentTimeMillis() - start));

start = System.currentTimeMillis();
List<User> list2 = new ArrayList<>(list);
Collections.sort(list2, User::compareAge);
System.out.println("Collections.sort: " + (System.currentTimeMillis() - start));

start = System.currentTimeMillis();
List<User> list3 = new ArrayList<>(list);
list3.stream().sorted(User::compareAge).collect(Collectors.toList());
System.out.println("Stream.sorted: " + (System.currentTimeMillis() - start));

输出结果:

List.sort: 18 Collections.sort: 18 Stream.sorted: 48

Stream 要略慢,因为多了两道转换的工序,但如果不是特别大的数据量,比如 1000 条数据的集合,这 3 个几乎性能一样,基本都在 1 毫秒内完成排序,对于普通小数据的排序可以闭着眼睛用了。

所以,你学废了吗?赶紧用在项目中吧,提升自己的硬实力,让同事对你刮目相看!再啰嗦一句,对 Java 8 新增的知识点还不会用的可以关注公众号:Java技术栈,在后台回复:java,Java 8+ 系列教程我都写了一堆了。

你还知道哪些排序技巧?欢迎留言分享~

本文所有完整示例源代码已经上传:

https://github.com/javastacks/javastack

欢迎 Star 学习,后面 Java 示例都会在这上面提供!

好了,今天的分享就到这里了,后面栈长会分享更多好玩的 Java 技术和最新的技术资讯,关注公众号Java技术栈第一时间推送,我也将主流 Java 面试题和参考答案都整理好了,在公众号后台回复关键字 "面试" 进行刷题。

最后,觉得我的文章对你用收获的话,动动小手,给个在看、转发,原创不易,栈长需要你的鼓励。

版权声明: 本文系公众号 "Java技术栈" 原创,转载、引用本文内容请注明出处,抄袭、洗稿一律投诉侵权,后果自负,并保留追究其法律责任的权利。








Spring Boot 定时任务开启后,怎么自动停止?
工作 3 年的同事不知道如何回滚代码
23 种设计模式实战(很全)
Spring Boot 保护敏感配置的 4 种方法!
面了个 5 年 Java,两个线程数据交换都不会
阿里为什么推荐使用 LongAdder?
新来一个技术总监:禁止戴耳机写代码。。
重磅!Spring Boot 2.7 正式发布
Java 18 正式发布,finalize 被弃用。
Spring Boot Admin 横空出世!
Spring Boot 学习笔记,这个太全了!



关注Java技术栈看更多干货



获取 Spring Boot 实战笔记!
浏览 17
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报