1. 前言

流是Java 8 API添加的一个新的抽象,称为流Stream,以一种声明性方式处理数据集合,侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式。

Stream流是从支持数据处理操作的源生成的元素序列,源可以是数组、文件、集合、函数。流不是集合元素,它不是数据结构并不保存数据,它的主要目的在于计算。

Stream流是对集合(Collection)对象功能的增强,与Lambda表达式结合,可以提高编程效率、间接性和程序可读性。

2. 操作符

流的操作类型主要分为两种:中间操作符、终端操作符

3 中间操作符

通常对于Stream的中间操作,可以视为是源的查询,并且是懒惰式的设计,对于源数据进行的计算只有在需要时才会被执行,与数据库中视图的原理相似;

Stream流的强大之处便是在于提供了丰富的中间操作,相比集合或数组这类容器,极大的简化源数据的计算复杂度

一个流可以跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用

这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的 filter、map 等

绍的 filter、map 等

流方法 含义 备注
filter 用于通过设置的条件过滤出元素
sorted 返回排序后的流
map 接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)
distinct 返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流
....

示例代码如下:

public static void main(String[] args) {
List<Person> list = new ArrayList<>(); // 名字 + 年龄。注意所有人的年龄有相同的
list.add(new Person("张三", 21));
list.add(new Person("张四", 21)); list.add(new Person("李三", 28));
list.add(new Person("李四", 28)); list.add(new Person("赵三", 23));
list.add(new Person("赵四", 23));
}

3.1 filter 过滤操作

用于通过设置的条件过滤出元素

// 注意 filter 的结果为true才会留下这条数据
// 同时filter是中间操作,不是最终操作
List<Person> personList = list.stream().filter((person) -> person.getAge() > 25).collect(Collectors.toList());
System.out.println(personList); 结果
[Person(name=李三, age=28), Person(name=李四, age=28)]

3.2 sorted 排序操作

返回排序后的流

// 正序排序
List<Person> personList = list.stream().sorted((x, y) -> x.getAge().compareTo(y.getAge())).collect(Collectors.toList());
System.out.println(personList); // 倒序排序
List<Person> personList1 = list.stream().sorted((x, y) -> y.getAge().compareTo(x.getAge())).collect(Collectors.toList());
System.out.println(personList1); 结果
[Person(name=张三, age=21), Person(name=张四, age=21), Person(name=赵三, age=23), Person(name=赵四, age=23), Person(name=李三, age=28), Person(name=李四, age=28)]
[Person(name=李三, age=28), Person(name=李四, age=28), Person(name=赵三, age=23), Person(name=赵四, age=23), Person(name=张三, age=21), Person(name=张四, age=21)]

3.3 map 操作

接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)

这是一个非常好用的操作,一定要重视

本质map是接受了一个Function的函数,如下:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
我们传进去key,按照一定规则给我们返回val List<Integer> collect = list.stream().map(person -> person.getAge()).collect(Collectors.toList());
System.out.println(collect); Set<Integer> collect1 = list.stream().map(person -> person.getAge()).collect(Collectors.toSet());
System.out.println(collect1); 结果
[21, 21, 28, 28, 23, 23]
[21, 23, 28]

3.4 distinct 去重操作

返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流

// 存在重复的年龄,去重
List<Integer> collect = list.stream().map(person -> person.getAge()).distinct().collect(Collectors.toList());
System.out.println(collect); 结果
[21, 28, 23]

4 终端操作符

Stream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流

一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。

终端操作的执行,才会真正开始流的遍历。如 count、collect 等

流方法 含义 备注
max 最大值
min 最小值
sum 求和
count 返回流中元素总数
findFirst 返回第一个元素
findAny 将返回当前流中的任意元素(注意为了效率会返回第一个符合的元素)
forEach 遍历流
reduce 可以将流中元素反复结合起来,得到一个值
collect 收集器,将流转换为其他形式 这个重点掌握,后面详细介绍
....

4.1 max操作

获取数据流中最大值

Person person = list.stream().max((o1, o2) -> o1.getAge().compareTo(o2.getAge())).get();
System.out.println(person); 结果
Person(name=李三, age=28)

4.2 min操作

获取数据流中最小值

Person person = list.stream().min((o1, o2) -> o1.getAge().compareTo(o2.getAge())).get();
System.out.println(person); 结果
Person(name=张三, age=21)

4.3 sum操作

求和

// 注意sum 必须是IntStream流才可以使用
int sum = list.stream().mapToInt(x -> x.getAge()).sum();
System.out.println(sum); 结果
144

4.4 count操作

求和

// 相当于list.size
long count = list.stream().count();
System.out.println("size = " + list.size() + ", count = " + count); 结果
size = 6, count = 6

4.5 findFirst操作

求和

Person person = list.stream().findFirst().get();
System.out.println(person); 结果
Person(name=张三, age=21)

4.6 findAny操作

求和

// 注意。findAny一定返回符合规则的第一个,因为其是流,所以为了效率,找到第一个,就立马返回
Person person = list.stream().findAny().get();
System.out.println(person); 结果
Person(name=张三, age=21)

4.7 forEach操作

求和

list.stream().forEach(person -> {
// 类似于for循环,可以做很多操作
// 如果年龄大于25,return相当于for循环的continue
if (person.getAge() > 25) {
return;
}
System.out.println(person);
}); 结果
Person(name=张三, age=21)
Person(name=张四, age=21)
Person(name=赵三, age=23)
Person(name=赵四, age=23)

4.8 reduce操作

可以将流中元素反复结合起来,得到一个值。

个人感觉reduce实用性并不是很好,并且理解成本挺高的,不建议使用

// 1. a + b ==> 赋值给 下一轮的a
// 2. (a + b) + b == > 赋值给 下一轮的a
// 相当于 sum = a + b + c + d + ....
int sum = list.stream().map(Person::getAge).reduce((a, b) -> {
System.out.println("a = " + a + " b = " + b);
return a + b;
}).get();
System.out.println("sum = " + sum); 结果
a = 21 b = 21
a = 42 b = 28
a = 70 b = 28
a = 98 b = 23
a = 121 b = 23
sum = 144

4.9 collect操作

收集器,将流转换为其他形式

非常重要的一个参数。后面会详细举例,一定要搞懂,尤其针对于toList,toMap等常见操作

// 相当于new了一个新的list
List<Person> collect = list.stream().collect(Collectors.toList());
System.out.println(collect); 结果
[Person(name=张三, age=21), Person(name=张四, age=21), Person(name=李三, age=28), Person(name=李四, age=28), Person(name=赵三, age=23), Person(name=赵四, age=23)]

5 Collect收集

Collector:结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力;

并在Collectors工具中提供了Collector接口的实现类

// collect请求参数是Collector,Collector是一个接口
<R, A> R collect(Collector<? super T, A, R> collector); Collectors是一个工具类,里面有Collector的具体实现类
/**
* Simple implementation class for {@code Collector}.
*
* @param <T> the type of elements to be collected
* @param <R> the type of the result
*/
static class CollectorImpl<T, A, R> implements Collector<T, A, R> {
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics; CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A,R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
} CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
} @Override
public BiConsumer<A, T> accumulator() {
return accumulator;
} @Override
public Supplier<A> supplier() {
return supplier;
} @Override
public BinaryOperator<A> combiner() {
return combiner;
} @Override
public Function<A, R> finisher() {
return finisher;
} @Override
public Set<Characteristics> characteristics() {
return characteristics;
}
}

5.1 toList操作

它将输入元素累积到一个新的List中。对于返回的List的类型、可变性、序列化能力或线程安全性并没有保证;

// 我们可以看到,本质上,toList其实就是传入了一个CollectorImpl的实现
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
} // 用法
List<Person> list1 = list.stream().collect(Collectors.toList());
System.out.println(list1); // 结果
[Person(name=张三, age=21), Person(name=张四, age=21), Person(name=李三, age=28), Person(name=李四, age=28), Person(name=赵三, age=23), Person(name=赵四, age=23)]

5.2 toSet操作

它将把输入元素累计到一个新的Set中。返回的Set的类型、可变性、序列化能力或线程安全性并没有确保

// 用法
Set<Person> set = list.stream().collect(Collectors.toSet());
System.out.println(set); // 结果
[Person(name=李四, age=28), Person(name=赵四, age=23), Person(name=张三, age=21), Person(name=赵三, age=23), Person(name=李三, age=28), Person(name=张四, age=21)]

5.3 toMap操作1

它将把输入元素累计到一个Map中,这个Map的键和值都是通过对输入元素应用给定的映射函数得到的

// 正常执行
Map<String, Person> map1 = list.stream().collect(Collectors.toMap(Person::getName, Function.identity()));
System.out.println(map1); // 报错 因为age是一个是重复的key
Map<Integer, Person> map2 = list.stream().collect(Collectors.toMap(Person::getAge, Function.identity()));
System.out.println(map2); // 结果
{张四=Person(name=张四, age=21), 李四=Person(name=李四, age=28), 张三=Person(name=张三, age=21), 赵三=Person(name=赵三, age=23), 赵四=Person(name=赵四, age=23), 李三=Person(name=李三, age=28)}
Exception in thread "main" java.lang.IllegalStateException: Duplicate key Person(name=张三, age=21)
at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
at java.util.HashMap.merge(HashMap.java:1254)
at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
at java.util.LinkedList$LLSpliterator.forEachRemaining(LinkedList.java:1235)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)

5.3 toMap操作2

如果key重复了,解决key重复问题

// 如果重复key,k1
Map<Integer, Person> map1 = list.stream().collect(Collectors.toMap(Person::getAge, Function.identity(), (k1, k2) -> k1));
System.out.println("map1 = " + JSON.toJSONString(map1)); // 如果重复key,k2
Map<Integer, Person> map2 = list.stream().collect(Collectors.toMap(Person::getAge, Function.identity(), (k1, k2) -> k2));
System.out.println("map2 = " + JSON.toJSONString(map2)); // 结果 重点看下结果的选择
map1 = {21:{"age":21,"name":"张三"},23:{"age":23,"name":"赵三"},28:{"age":28,"name":"李三"}}
map2 = {21:{"age":21,"name":"张四"},23:{"age":23,"name":"赵四"},28:{"age":28,"name":"李四"}}

5.3 toMap操作3

解决map返回的顺序问题

// 返回无序的HashMap
Map<Integer, Person> map1 = list.stream().collect(Collectors.toMap(
Person::getAge,
Function.identity(),
(k1, k2) -> k1,
HashMap::new));
System.out.println("map1 = " + JSON.toJSONString(map1)); // 返回有序的LinkedHashMap
Map<Integer, Person> map2 = list.stream().collect(Collectors.toMap(
Person::getAge,
Function.identity(),
(k1, k2) -> k1,
LinkedHashMap::new));
System.out.println("map2 = " + JSON.toJSONString(map2)); // 结果 重点看下结果的选择
map1 = {21:{"age":21,"name":"张三"},23:{"age":23,"name":"赵三"},28:{"age":28,"name":"李三"}}
map2 = {21:{"age":21,"name":"张三"},28:{"age":28,"name":"李三"},23:{"age":23,"name":"赵三"}}

5.4 joining

连接字符串

// 默认没有字符连接
String s1 = list.stream().map(Person::getName).collect(Collectors.joining());
System.out.println("s1 = " + s1); // 以-连接字符
String s2 = list.stream().map(Person::getName).collect(Collectors.joining("-"));
System.out.println("s2 = " + s2); // 以-连接字符, 同时增加前缀和后缀
String s3 = list.stream().map(Person::getName).collect(Collectors.joining("-", "prefix", "suffix"));
System.out.println("s3 = " + s3); // 结果
s1 = 张三张四李三李四赵三赵四
s2 = 张三-张四-李三-李四-赵三-赵四
s3 = prefix张三-张四-李三-李四-赵三-赵四suffix

5.5 counting

计算list里面的数量

// 默认没有字符连接
System.out.println("sum = " + list.size()); Long sum1 = list.stream().collect(Collectors.counting());
System.out.println("sum1 = " + sum1); long sum2 = list.stream().count();
System.out.println("sum2 = " + sum2); // 结果
sum = 6
sum1 = 6
sum2 = 6

5.6 summarizingInt

统计一些常见的数据

IntSummaryStatistics statistics = list.stream().collect(Collectors.summarizingInt(x -> x.getAge()));
System.out.println(statistics); // 结果
IntSummaryStatistics{count=6, sum=144, min=21, average=24.000000, max=28}

5.7 groupingBy

它的作用是实现"分组"操作,根据给定的分类函数将输入元素分组,并把分组的结果存储在一个Map中并返回。

// key是age,value是list
Map<Integer, List<Person>> map1 = list.stream().collect(Collectors.groupingBy(person -> person.getAge()));
System.out.println(map1); // key是age,value是name的list
Map<Integer, List<String>> map2 = list.stream().collect(
Collectors.groupingBy(person -> person.getAge(), Collectors.mapping(person -> person.getName(), Collectors.toList())));
System.out.println(map2); // key是age,value是list, 且是有序的
Map<Integer, List<Person>> map3 = list.stream().collect(
Collectors.groupingBy(Person::getAge, () -> new LinkedHashMap<>(), Collectors.toList()));
System.out.println(map3); // key是age,value是name的list, 且是有序的
Map<Integer, List<String>> map4 = list.stream().collect(
Collectors.groupingBy(Person::getAge, LinkedHashMap::new, Collectors.mapping(Person::getName, Collectors.toList())));
System.out.println(map4); // 结果
{21=[Person(name=张三, age=21), Person(name=张四, age=21)], 23=[Person(name=赵三, age=23), Person(name=赵四, age=23)], 28=[Person(name=李三, age=28), Person(name=李四, age=28)]}
{21=[张三, 张四], 23=[赵三, 赵四], 28=[李三, 李四]}
{21=[Person(name=张三, age=21), Person(name=张四, age=21)], 28=[Person(name=李三, age=28), Person(name=李四, age=28)], 23=[Person(name=赵三, age=23), Person(name=赵四, age=23)]}
{21=[张三, 张四], 28=[李三, 李四], 23=[赵三, 赵四]}

Java -- Stream流用法的更多相关文章

  1. 一文带你入门Java Stream流,太强了

    两个星期以前,就有读者强烈要求我写一篇 Java Stream 流的文章,我说市面上不是已经有很多了吗,结果你猜他怎么说:"就想看你写的啊!"你看你看,多么苍白的喜欢啊.那就&qu ...

  2. 全面吃透JAVA Stream流操作,让代码更加的优雅

    全面吃透JAVA Stream流操作,让代码更加的优雅 在JAVA中,涉及到对数组.Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行 ...

  3. Java8用了这么久了,Stream 流用法及语法你都知道吗?

    1.简介 Stream流 最全的用法Stream 能用来干什么?用来处理集合,通过 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询,Stream API 提供了一 ...

  4. 深度掌握 Java Stream 流操作,让你的代码高出一个逼格!

    概念 Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选.排序.聚合等. Stream 的操作符大体上分为两种:中间操作符和终止操作符 中 ...

  5. Java Stream常见用法汇总,开发效率大幅提升

    本文已经收录到Github仓库,该仓库包含计算机基础.Java基础.多线程.JVM.数据库.Redis.Spring.Mybatis.SpringMVC.SpringBoot.分布式.微服务.设计模式 ...

  6. Java Stream流排序null以及获取指定条数数据

    Java8的Stream流的一些用法, //排序 carerVehEntityList = carerVehEntityList.stream().sorted( Comparator.compari ...

  7. Java Stream 流如何进行合并操作

    1. 前言 Java Stream Api 提供了很多有用的 Api 让我们很方便将集合或者多个同类型的元素转换为流进行操作.今天我们来看看如何合并 Stream 流. 2. Stream 流的合并 ...

  8. Java Stream 流(JDK 8 新特性)

    什么是 Steam Java 8 中新增了 Stream(流)来简化集合类的使用,Stream 本质上是个接口,接口中定义了很多对 Stream 对象的操作. 我们知道,Java 中 List 和 S ...

  9. Java | Stream流、泛型、多线程 | 整理自用

    1.lambda 表达式 lambda 的延迟执行 可以对程序进行优化,尤其是使用 if {} else {} 条件判断,先判断条件是否成立,再传入计算好的参数. functionName( para ...

  10. Java Stream流的使用

    流相关的方法可以分为三种类型,分别是:获取Stream流.中间方法.终结方法.中间方法会返回当前流,可以方便的进行链式调用. 流不可重复使用,否则会报错: java.lang.IllegalState ...

随机推荐

  1. html表格基本标签

    1.<table>表签 <table>...</table>标签用于在html文档中后创建表格.它包含表名和表格本身内容的代码. 2.<tr>标签 &l ...

  2. openpyxl 统一表格样式

    # 统一表格样式 rows = ws.max_row columns = ws.max_column # print(rows) # print(columns) for row in range(1 ...

  3. searchsploit用法

    kali里面自带这个工具,用来搜索www.exploit-db.com上面的漏洞库,在由于是提前内置了数据库所以不联网也可以用. 使用之前先更新一下确保是最新的漏洞库 searchsploit --u ...

  4. ACl与ACL实验

    ACl与ACL实验 ACL 1,ACL概述及 产生的背景 ACL: access list 访问控制列表 2,ACL应用 ACL两种应用: 应用在接口的ACL-----过滤数据包(原目ip地址,原目 ...

  5. Maven资源导出问题所需配置

    <!--在build中配置resources,来防止我们资源导出失败的问题--> <build> <resources> <resource> < ...

  6. Fork me on GitHub彩带添加方法

    在博客添加GitHub彩带的方法 针对博客园博客追加如图彩带的方法 依次进入 管理 → 设置 → 页首Html代码 将如下代码粘贴在该处 <a target="_blank" ...

  7. 《SQL与数据库基础》10. 存储引擎

    目录 存储引擎 MySQL体系结构 存储引擎简介 三种经典存储引擎 InnoDB 逻辑存储结构 MyISAM Memory 区别及特点 存储引擎选择 本文以 MySQL 为例 存储引擎 MySQL体系 ...

  8. 关闭k8s的pod时减小对服务的影响

    在应用程序的整个生命周期中,正在运行的 pod 会由于多种原因而终止.在某些情况下,Kubernetes 会因用户输入(例如更新或删除 Deployment 时)而终止 pod.在其他情况下,Kube ...

  9. Solution Set -「ARC 124」

    「ARC 124A」LR Constraints Link. 我们可以把 \(1\sim n\) 个盒子里能放的球的编号集合全部求出来.然后就直接来. 注意题目已经给出了 \(k\) 个球的位置,所以 ...

  10. Vite+ts+springboot项目集成2

    项目集成 集成element-plus 官网地址: 安装图标库 pnpm install element-plus @element-plus/icons-vue 入口文件main.ts全局安装ele ...