Java Stream 实用特性:排序、分组和 teeing

这篇具有很好参考价值的文章主要介绍了Java Stream 实用特性:排序、分组和 teeing。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

排序

基本数据类型排序

基本数据类型就是字符串、整型、浮点型这些,也就是要排序的列表中的元素都是这些基本类型的,比如 List<Integer>的。

下面就用一个整型列表举例说明。

正序排序

正序排序,也可以叫做按照自然顺序排序,对于整型来说就是从小到大的。

List<Integer> integerList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
  integerList.add(i);
}
List<Integer> collect = integerList.stream()
  .sorted()
  .collect(Collectors.toList());
System.out.println(collect);
复制代码

输出结果是 [0, 1, 2, 3, 4],这很简单没什么好说的。

倒序排序

List<Integer> integerList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
  integerList.add(i);
}
List<Integer> collect2 = integerList.stream()
  .sorted(Comparator.reverseOrder())
  .collect(Collectors.toList());
System.out.println(collect2);
复制代码

倒序排就是从大到小排序,也很简单在 sorted()方法中添加 Comparator.reverseOrder() 就可以了。

非基本类型实体排序

基本类型的列表排序很简单,但是在实际项目中用到的情况不太多,经常用到的还是我们自定义类型的排序,比如项目中有一个用户实体、一个订单实体、一个产品实体等。

首先定一个Product实体类:

import lombok.Data;

/**
 * @author fengzheng
 */
@Data
public class Product {
    /**
     * 唯一标示
     */
    private Integer id;

    /**
     * 所属类别
      */
    private Integer type;

    /**
     * 商品名称
     */
    private String name;
    
    /**
     * 价格
      */
    private Double price;
    
}
复制代码

按某一个字段排序

对应到我上面定义的这个实体,可以是按照 id 排序,或者按照 price排序。

正序排序

假设按照 price从小到大排序,也就是按照价格由低到高排序。

对应到 SQL 上,可以表示成这样的。

select * from product order by price asc
复制代码

那用 Stream 实现呢?

List<Product> productList = initProductList();
List<Product> collect = productList.stream()
  .sorted(Comparator.comparing(Product::getPrice))
  .collect(Collectors.toList());
复制代码

等价于

List<Product> collect = productList.stream()
  .sorted((x,y) -> x.getPrice().compareTo(y.getPrice()))
  .collect(Collectors.toList());
复制代码

等价于

Comparator<Product> comparator = new Comparator<Product>() {
  @Override
  public int compare(Product p1, Product p2) {
    return p1.getPrice().compareTo(p2.getPrice());
  }
};

List<Product> collect = productList.stream()
  .sorted((p1, p2) -> comparator.compare(p1, p2))
  .collect(Collectors.toList());
复制代码

这里面主要由我们提供自定义的就是函数式接口 Comparator,凡是实现了 compare () 方法的都可以。

上面我们自定义的这个 comparator,重载了 compare方法。compare 方法的返回值规则:

  1. 前者小于后者,返回 -1;
  2. 前者大于后者,返回 1;
  3. 前者等于后者,返回 0;

所以可以理解为,如果 compare 返回的是 1, Stream 就会交换两个实体的位置。所以这样一来,倒序排序就很好整了。

倒序排序

可以这样写,使用 reversed() 方法

List<Product> collect = productList.stream()
  .sorted(Comparator.comparing(Product::getPrice).reversed())
  .collect(Collectors.toList());
复制代码

或者可以

List<Product> collect = productList.stream()
	.sorted(Comparator.comparing(Product::getPrice,Comparator.reverseOrder()))
  .collect(Collectors.toList());
复制代码

还可以直接直接使用compare ,倒序排序就简单了,稍微改一下就好了。

直接用 Lambda 表达式的写法

List<Product> collect = productList.stream()
  .sorted((x,y) -> y.getPrice().compareTo(x.getPrice()))
  .collect(Collectors.toList());
复制代码

等价于,抽取出自定义 Comparator的方法

Comparator<Product> comparator = new Comparator<Product>() {
            @Override
            public int compare(Product p1, Product p2) {
                return p2.getPrice().compareTo(p1.getPrice());
            }
};

List<Product> collect = productList.stream()
  .sorted((p1, p2) -> comparator.compare(p1, p2))
  .collect(Collectors.toList());
复制代码

倒序和正序的区别其实就是将 compare()前后两个元素的位置对调一下。

对于大小比较的可以直接用 compare()方法,但是有一些情况可能不止这么简单。没有关系,我们不是可以自定义 Comparator 吗,在 Comparator 重写的 compare 方法中可以加入我们的排序逻辑,不管多么特殊、多么复杂,只要返回一个 int 类型的就可以了。

按照多个字段排序

还有一些情况要按照两个甚至多个字段排序,一个主排序,一个次要排序。比如我们想要先按 type 升序,再按 price 降序。

对应到 SQL 上就像这样

select * from product order by type asc,price desc
复制代码

那用 Stream 来实现是怎么样的呢?用 thenComparing连接多个要排序的属性。

List<Product> collect = productList.stream().sorted(Comparator.comparing(Product::getType).thenComparing(Product::getPrice, Comparator.reverseOrder())).collect(Collectors.toList());
复制代码

或者还可以定义两个 Comparator

Comparator<Product> typeComparator = new Comparator<Product>() {
  @Override
  public int compare(Product p1, Product p2) {
    return p1.getType().compareTo(p2.getType());
  }
};

Comparator<Product> priceComparator = new Comparator<Product>() {
  @Override
  public int compare(Product p1, Product p2) {
    return p2.getPrice().compareTo(p1.getPrice());
  }
};
List<Product> collect = productList.stream()
  .sorted(typeComparator.thenComparing(priceComparator))
  .collect(Collectors.toList());
复制代码

怎么样,一点难度都没有吧。

分组

除了排序,还有一个非常有用而且经常会用的功能就是分组功能。分组功能是 collect()方法提供的功能,返回值是一个字典类型。

根据 type 进行分组

对应到 SQL 中就是下面这样

select * from product group by type
复制代码

用 Stream 来实现呢,就是下面这样子

Map<Integer, List<Product>> map = productList.stream()
  .collect(Collectors.groupingBy(Product::getType));
复制代码

最后生成的对象是一个 Map 类型,key 是用来作为分组依据的字段值,value 是一个列表,也就是同一组的对象集合。在这个例子中,key 就是 product 对象的 type 属性,value 就是 type 相同的 Product 对象的集合。

如果只是求出每一个组所包含的对象个数,可以这样实现,不用遍历 Map 这么麻烦。

Map<Integer, Long> map = productList.stream()
  .collect(Collectors.groupingBy(Product::getType, Collectors.counting()));
复制代码

根据两个或多个字段分组

有时候我们可能会根据不止一个字段进行分组,比如想按照类别相同且价格相同进行分组。

Map<String, List<Product>> map = productList.stream()
                .collect(Collectors.groupingBy(p -> p.getType() + "|" + p.getPrice()));
复制代码

等价于,将分组依据单独抽取出一个方法,这样就可以加入比较复杂的逻辑了,最终返回的是一个字符串。

Map<String, List<Product>> map = productList.stream()
   .collect(Collectors.groupingBy(p -> buildGroupKey(p)));

private static String buildGroupKey(Product p) {
   return p.getType() + "|" + p.getPrice();
}
复制代码

为什么两个字段之间要加一个分隔符呢,这是因为有些情况我们还会用到分组依据中的某一个字段,加入分隔符之后方便拆分字符串。当然了,也可以拿到这个分组下的任意一个元素获取。

嵌套分组

上面的根据多个字段分组是把多个字段当做同一级别并且的关系处理,还有一些时候呢,我们想要先按一个字段分组,再分组中再按另一个字段分组,这样就形成了一个嵌套关系,比如先按 type 分组,再按 price 分组,这就相当于是一个二维字典(两个层级)。

Map<Integer, Map<Double, List<Product>>> map = productList.stream()
  .collect(Collectors.groupingBy(Product::getType, Collectors.groupingBy(Product::getPrice)));
复制代码

通过返回值类型就可以看出来是怎么样的一个层级关系。

teeing()

这是 JDK 12 才出来的方法,所以要用这个方法,比如在 JDK12 以上才行。它的作用是对两个收集器(Collectors)的结果进行处理。上面的例子中,求出最高价格和最低价格的,并输出为一个字符串,将两个价格用 ~符号连接。

String result = productList.stream().collect(Collectors.teeing(
  Collectors.minBy(Comparator.comparing(Product::getPrice)),
  Collectors.maxBy(Comparator.comparing(Product::getPrice)),
  (min, max) -> {
    return min.get().getPrice() + "~" + max.get().getPrice();
  }
));
System.out.println(result);
复制代码

最终得到的结果是一个字符串,打印如下,测试数据没有做小数位限制。

4.347594572793579~89.43160979811124
复制代码

最终的返回类型根据teeing() 方法的最后一个参数的返回结果而定。 min 和 max 这两个参数就是前两个收集器 Collectors.minByCollectors.maxBy的返回结果,因为返回类型是 Optional ,所以再取值的时候要加上 get

总结

Stream 提供了很丰富的 API ,最大的好处是让我们可以少写很多代码,熟练掌握之后,可以在一些对应的场景快速实现我们想要的逻辑。

有同学说,不行啊,又是 filter 、又是 collect、又是 Collectors ,根本记不住啊。没关系,记不住也正常,它本来就是一个工具,我们其实只要知道它可以实现什么功能,具体的用法可以随用随查吗。这不,我的这两篇文章就可以放进收藏夹里,什么时候用,什么时候打开查一下就好了。

下次碰到类似的场景,记得用 Stream 试一下吧。文章来源地址https://www.toymoban.com/news/detail-777025.html

到了这里,关于Java Stream 实用特性:排序、分组和 teeing的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java stream 分组操作

    根据单/多字段分组 单字段分组可以直接使用指定字段,多字段分组则采用拼接Key的形式 单字段: 多字段: 多层级: 测试

    2024年02月15日
    浏览(42)
  • Java stream多字段分组(groupingBy)

    近期的项目里,遇到一个需求:对于含有多个元素的 ListPerson ,按照其中的某几个属性进行分组,比如 Persion::getAge 、 Persion::getType 、 Persion::getGender 等字段。下面就让我们讨论一下如何比较优雅的按多字段进行分组groupingBy。 Stream 是Java8的一个新特性,主要用户集合数据的处

    2024年02月13日
    浏览(44)
  • java stream实现分组BigDecimal求和,自定义分组求和

    随着微服务的发展,越来越多的sql处理被放到java来处理,数据库经常会使用到对集合中的数据进行分组求和,分组运算等等。 那怎么样使用java的stream优雅的进行分组求和或运算呢? 这里测试数据学生,年龄类型是Integer,身高类型是BigDecimal,我们分别对身高个年龄进行求和

    2024年02月01日
    浏览(45)
  • Java Stream流 Map 分组方式汇总

    java老式的分组方式(对list for循环然后 if判断 放入map) 代码复杂,易读性差,维护性差,故本文汇总了Stream流中的分组方法供大家参考,如对您有帮助,请抬抬高贵的小手点个赞吧,欢迎大佬留下高见 (以下方法默认都是java8的方法,java9新增方法有标注) 按颜色分组 打印Map结果: {红色

    2024年02月06日
    浏览(43)
  • Java8 Stream 之groupingBy 分组讲解

    本文主要讲解:Java 8 Stream之Collectors.groupingBy()分组示例 Collectors.groupingBy() 分组之常见用法 功能代码: /**      * 使用java8 stream groupingBy操作,按城市分组list      */     public void groupingByCity() {         MapString, ListEmployee map = employees.stream().collect(Collectors.groupingBy(Employee::getCi

    2024年02月13日
    浏览(46)
  • Java中用Stream分组并求各组数量

    比如给了我一个班级的所有人的姓氏和姓名对象集合,根据这个集合求出各姓氏有多少人。直接上代码。 User的实体类对象为 输出的结果为:   即姓张的有3个,姓李的有两个。 详解: 将该集合的stream流用Collectors对象转成Map,用User对象的firstName作为Key,默认键值为1,在处

    2024年02月12日
    浏览(42)
  • Java Stream 处理分组后取每组最大&Stream流之list转map、分组取每组第一条&Java 8 Collectors:reducing 示例(List分组取最值)

    有一个需求功能:先按照某一字段分组,再按照另外字段获取最大的那个 先根据appId分组,然后根据versionSort取最大. JDK1.8推出的stream流能极大的简化对集合的操作,让代码更美观,老规矩,直接上代码。 取list中对象的某个属性作为唯一key,对象作为value形成一个map集合,能

    2024年02月16日
    浏览(60)
  • Java Stream流实现多字段分组groupingBy操作

    近期的项目里,遇到一个需求:对于含有多个元素的List,按照其中的某几个属性进行分组,比如Report::getPersonID、Report::getSchoolYear、Report::getDataType等字段。下面就让我们讨论一下如何比较优雅的按多字段进行分组groupingBy。 利用单个字段进行分组 如上面的Report类,如果对于其

    2024年02月07日
    浏览(59)
  • Java新特性:Stream流式编程

    Java新特性:Stream流式编程 Stream 流是 Java8 提供的新功能,是对集合对象功能的增强,能对集合对象进行各种非常便利、高效的聚合操作,或大批量数据操作。Stream 流以一种声明性方式处理数据集合,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理

    2024年02月15日
    浏览(44)
  • Java Stream排序

    我们在处理数据的时候经常会需要进行排序后再返回给前端调用,比如按照时间升序排序,前端展示数据就是按时间先后进行排序。 这里可以运用stream的 sorted()方法来进行自定义的排序  这里举例数据是一个list集合,ListDatalist,Data实体类中有许多属性,其中有时间字段,

    2024年02月13日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包