使用 Stream 处理集合数据【Java 1.8 新特性】

使用 Stream 处理集合数据【Java 1.8 新特性】

Stream 是Java 8中引入的一个重要概念,它提供了对集合对象进行一系列操作的新方式,包括筛选、转换、聚合等。Stream API以声明式方式提供了对数据集合的高效操作,并且可以并行处理数据。

首先构建一个类,下面举例用得着

java 复制代码
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
    private String name; // 姓名
    private Integer age; // 年龄
    private Double score;// 积分
}

然后构建一个混乱的集合

java 复制代码
ArrayList<Customer> customers = new ArrayList(){{
            add(new Customer("user01",10,7000.0));
            add(new Customer("user02",10,2000.0));
            add(new Customer("user03",20,3000.0));
            add(new Customer("user04",20,4000.0));
            add(new Customer("user05",30,5000.0));
            add(new Customer("user06",30,6000.0));
            add(new Customer("user07",40,1000.0));
        }};

中间操作

[!NOTE]

带有⭐️ ⭐️⭐️ 的方法在平常开发中使用频率高

带有⭐️ ⭐️的方法在平常开发中使用频率中等

带有⭐️ 的方法在平常开发中使用频率低

不带⭐️ 的方法在平常开发中几乎不会用到

filter 过滤 ⭐️⭐️⭐️

java 复制代码
// 筛选出年龄大于20,积分大于1000 的用户并返回
List<Customer> collect = customers.stream()
  .filter(c -> c.getAge() > 20 && c.getScore() > 1000 )
  .collect(Collectors.toList());

对于 filter ,需掌握3个知识点

  1. filter 方法可以多次调用,比如先按年龄过滤,再按积分过滤

    java 复制代码
    List<Customer> collect = customers.stream()
      .filter(c -> c.getAge() > 20)
      .filter(c -> c.getScore() > 1000 )
      .collect(Collectors.toList());

    [!CAUTION]

    filter 的多次调用中,当前是在上一次过滤的基础上再过滤,举个例子:如果一条数据满足条件b但不满足条件a,而过滤顺序是条件a在前条件b在后,那么这条数据在条件a处就被过滤掉了,不会轮到条件b处理。

map 映射 ⭐️ ⭐️⭐️

将流中的每个元素映射到另一个元素,这么说可能有点抽象,通俗来说就是操作集合中的对象。

java 复制代码
// 将每个用户的积分+1
customers.stream().map(customer -> {
            customer.setScore(customer.getScore()+1);
            return customer;
        }).collect(Collectors.toList());

结果如下

0 = {Customer@706} "Customer(name=user01, age=10, score=7001.0)"

1 = {Customer@707} "Customer(name=user02, age=10, score=2001.0)"

2 = {Customer@708} "Customer(name=user03, age=20, score=3001.0)"

3 = {Customer@709} "Customer(name=user06, age=20, score=4001.0)"

4 = {Customer@710} "Customer(name=user06, age=30, score=5001.0)"

5 = {Customer@711} "Customer(name=user06, age=30, score=6001.0)"

6 = {Customer@712} "Customer(name=user06, age=40, score=1001.0)"

对于 map ,需掌握3个知识点

  1. map 方法可以多次调用,比如先处理年龄+1,再处理积分+1
  2. map 的lambda表达式有返回值,需要return,至于是否需要使用一个新集合作为变量接收结果,一般我不建议用,因为"新集合"是浅拷贝,修改了原数据,"新集合"中的数据也会跟着变
  3. 结尾不用.collect(Collectors.toList()),操作是无效的~

flatMap 将流中的每个元素替换为目标元素的流

flatMap 用于将流中的每个元素(这些元素通常是集合或数组)转换成流,然后将这些流连接起来形成一个单一的流。如下代码

java 复制代码
// 如果customer的年龄大于20,就将当前的customer对象复制2份,否则复制3份
List<Customer> collect = customers.stream().flatMap(customer -> {
            return customer.getAge() > 20?
                    IntStream.range(0, 2).mapToObj(
                            i -> new Customer(customer.getName(), customer.getAge(), customer.getScore()))
                    : IntStream.range(0, 3).mapToObj(
                            i -> new Customer(customer.getName(), customer.getAge(), customer.getScore()));
        }).collect(Collectors.toList());

对于 flatMap ,需掌握3个知识点

  1. flatMap 方法可以多次调用
  2. lambda表达式返回的对象必须是一个Stream<U>类型
  3. 结尾不用.collect(Collectors.toList()),操作是无效的~

[!TIP]

平时开发几乎用不到 flatMap 方法,处理 List< List<Obj> > 型的数据用到过。

limit 截取 ⭐️ ⭐️

它用于截取流中的前 n 个元素,返回一个由原始流的前 n 个元素组成的新流。如果流中的元素少于 n 个,则返回包含所有元素的流

java 复制代码
// 截取customers前两个元素
List<Customer> collect = customers.stream().limit(2).collect(Collectors.toList());

结果如下

0 = {Customer@707} "Customer(name=user01, age=10, score=7000.0)"

1 = {Customer@708} "Customer(name=user02, age=10, score=2000.0)"

对于 limit ,需掌握3个知识点

  1. limit 方法可以多次调用(一般不会连着调多次)
  2. limit 是浅拷贝,修改了原数据,"新集合"中的数据也会跟着变
  3. 结尾不用.collect(Collectors.toList()),操作是无效的~

sorted 排序 ⭐️ ⭐️⭐️

java 复制代码
/**
 * 顺序排序(两种方案均可)
 * */
List<Customer> collect = customers.stream()
                .sorted(Comparator.comparingInt(Customer::getAge))
                .collect(Collectors.toList());
/**
 * 倒序排序,加 reversed 即可
 * */
List<Customer> collect = customers.stream()
                .sorted(Comparator.comparingInt(Customer::getAge).reversed())
                .collect(Collectors.toList());

对于 sorted ,需掌握3个知识点

  1. sorted 是浅拷贝,修改了原数据,"新集合"中的数据也会跟着变

  2. 结尾不用.collect(Collectors.toList()),操作是无效的~

  3. sorted 方法可以多次调用,比如先按年龄排,再按积分排

    java 复制代码
    List<Customer> collect = customers.stream()
                    .sorted(Comparator.comparingInt(Customer::getAge))
                    .sorted(Comparator.comparingDouble(Customer::getScore).reversed())
                    .collect(Collectors.toList());

    [!CAUTION]

    但是哈,这种排序会扰乱上一步排序的结果,比如上述代码,其实我希望是在年龄排序的基础上,再排序积分,结果如下,可以看出年龄排序已经被打乱了~,所以一般不会用到多次调用 sorted

    0 = {Customer@734} "Customer(name=, age=10, score=7000.0)"

    1 = {Customer@739} "Customer( age=30, score=6000.0)"

    2 = {Customer@738} "Customer( age=30, score=5000.0)"

    3 = {Customer@737} "Customer( age=20, score=4000.0)"

    4 = {Customer@736} "Customer( age=20, score=3000.0)"

    5 = {Customer@735} "Customer( age=10, score=2000.0)"

    6 = {Customer@740} "Customer( age=40, score=1000.0)"

    既然如此,那么如何达到要求呢?当然是从Comparator 上下手了,在第一个排序条件后跟.thenComparing...,如下

    java 复制代码
    List<Customer> collect = customers.stream()
                 .sorted(
    										Comparator.comparingInt(Customer::getAge)
    										.thenComparingDouble(Customer::getScore)
    								).collect(Collectors.toList());

    排序结果如下,达到要求了~

    0 = {Customer@737} "Customer( age=10, score=2000.0)"

    1 = {Customer@738} "Customer( age=10, score=7000.0)"

    2 = {Customer@739} "Customer( age=20, score=3000.0)"

    3 = {Customer@740} "Customer( age=20, score=4000.0)"

    4 = {Customer@741} "Customer( age=30, score=5000.0)"

    5 = {Customer@742} "Customer( age=30, score=6000.0)"

    6 = {Customer@743} "Customer( age=40, score=1000.0)"

    [!NOTE]

    • thenComparing支持多种数据类型排序:int,long,double
    • Comparator 也支持链式编程

mapToDouble / mapToInt / mapToLong 提取数字 ⭐️⭐️

用于将流中的每个元素映射到一个 double 值。这个操作通常用于将流中的元素转换成数值型数据,以便进行数值计算。这些 方法接受一个函数作为参数,这个函数会被应用到流中的每个元素上,并将每个元素转换成一个 数字 值。这个方法返回一个 DoubleStream / LongStream / IntStream,可以被进一步用于数值操作,如求和、平均值计算等。

java 复制代码
// 把customers中的各元素的积分值提取出来装入一个list
List<Double> collect = customers.stream().mapToDouble(Customer::getScore).boxed().collect(Collectors.toList());
// 计算积分总和
double totalScore = customers.stream().mapToDouble(Customer::getScore).sum();
// 获取最大积分
double maxScore = customers.stream().mapToDouble(Customer::getScore).max();
// 获取最小积分
double minScore = customers.stream().mapToDouble(Customer::getScore).min();
// 计算平均积分
double minScore = customers.stream().mapToDouble(Customer::getScore).average();

方法还有很多,感兴趣可以去探索,这里就不一一例举了~

对于 mapToDouble / mapToInt / mapToLong ,需掌握1个知识点

  1. mapToDouble / mapToInt / mapToLong 可以链式编程,但是一般开发不这么做

终止操作

forEach:遍历流中元素⭐️⭐️⭐️

java 复制代码
customers.stream().forEach(
                customer -> {
                  	Double score = customer.getScore();
                    customer.setScore(score + 1);
                }
        );

对于 forEach ,需掌握2个知识点

  1. forEach 中如果修改数据,影响原集合
  2. forEach 方法仅能一次调用

collect:将流转换成其他形式(如集合)⭐️⭐️⭐️

需掌握 Collectors工具类,它提供了多种收集方式

  • Collectors.toList():将流收集到一个新的 List 中 ⭐️⭐️⭐️
  • Collectors.toSet():将流收集到一个新的 Set 中,这会去除重复元素 ⭐️⭐️⭐️
  • Collectors.toCollection():将流收集到给定的 Collection
  • Collectors.joining():将流中的元素连接成一个字符串 ⭐️
  • Collectors.toMap():将流元素收集到一个 Map 中 ⭐️⭐️⭐️
  • Collectors.groupingBy():根据某个属性对流元素进行分组,结果收集到一个 Map 中⭐️
  • Collectors.reducing():通过某个连接动作(如加法)将所有元素汇总成一个汇总结果⭐️
  • Collectors.collectingAndThen():在另一个收集器的结果上执行映射操作
  • 除了使用 Collectors 提供的静态方法,开发者还可以自定义收集器(我没这么玩过,感兴趣的可以研究一下)

对于 collect ,需掌握1个知识点

  1. collect 方法仅能一次调用

reduce:通过某个连接动作将所有元素汇总成一个汇总结果 ⭐️⭐️

它用于通过某个连接动作将所有元素汇总成一个汇总结果。reduce 方法可以用于多种类型的归约操作,比如求和、求最大值、求最小值等。【做大数据项目时用的最多】

java 复制代码
// 计算customers中各元素积分总和
double reduce = customers.stream().mapToDouble(i -> i.getScore()).reduce(0, (a, b) -> a + b);
// 计算customers中积分最大值
double reduce = customers.stream().mapToDouble(i -> i.getScore()).reduce(0, (a, b) -> a > b ? a:b);
// 计算customers中积分最小值
double reduce = customers.stream().mapToDouble(i -> i.getScore()).reduce(0, (a, b) -> a < b ? a:b);

对于 reduce ,需掌握1个知识点

  1. reduce 方法仅能一次调用

allMatchanyMatchnoneMatch:检查流中的元素是否与给定的条件匹配

java 复制代码
// 是否所有customers中元素的积分数值都大于10
boolean b = customers.stream().allMatch(i -> i.getScore() > 10);
// customers中是否存在有的元素的积分数值大于10
boolean b = customers.stream().anyMatch(i -> i.getScore() > 10);
// customers中所有的元素的积分数值都不大于10
boolean b = customers.stream().noneMatch(i -> i.getScore() > 10);

对于 allMatchanyMatchnoneMatch ,需掌握1个知识点

  1. allMatchanyMatchnoneMatch 方法仅能一次调用

count:返回流中元素的数量 ⭐️

java 复制代码
// 统计customers中年龄大于20的数据量
long count = customers.stream().filter( i -> i.getAge() > 20 ).count();

对于 count ,需掌握1个知识点

1. `count` 方法仅能一次调用

findFirst、findAny:返回流中的第一个或任意一个元素 ⭐️

java 复制代码
Customer any = customers.stream().findAny().get();
Customer first = customers.stream().findFirst().get();

对于 findFirstfindAny ,需掌握3个知识点

  1. findFirstfindAny 方法仅能一次调用
  2. Optional 类的 orElse 方法允许你提供一个默认值,如果 Optional 为空,则返回这个默认值
  3. orElseGet :类似于 orElse,但接受一个 Supplier 函数,当 Optional 为空时,会调用这个函数来生成默认值

先记录到这里,后续有总结会持续补上

相关推荐
繁依Fanyi5 分钟前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
慧都小妮子16 分钟前
Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图
java·pdf·.net
m512720 分钟前
LinuxC语言
java·服务器·前端
IU宝24 分钟前
C/C++内存管理
java·c语言·c++
瓜牛_gn25 分钟前
依赖注入注解
java·后端·spring
hakesashou26 分钟前
Python中常用的函数介绍
java·网络·python
佚先森35 分钟前
2024ARM网络验证 支持一键云注入引流弹窗注册机 一键脱壳APP加固搭建程序源码及教程
java·html
古月居GYH1 小时前
在C++上实现反射用法
java·开发语言·c++