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. .NET程序的 GDI句柄泄露 的再反思

    一:背景 1. 讲故事 上个月我写过一篇 如何洞察 C# 程序的 GDI 句柄泄露 文章,当时用的是 GDIView + WinDbg 把问题搞定,前者用来定位泄露资源,后者用来定位泄露代码,后面有朋 ...

  2. Cilium系列-4-Cilium本地路由

    系列文章 Cilium 系列文章 前言 在前文中我们提到, cilium install 默认安装后, Cilium 功能启用和禁用情况如下: datapath mode: tunnel: 因为兼容性 ...

  3. python2.7源码安装方式

    安装python2.7 下载Python 2.7, 下载地址 解压安装 tar -xzvf Python-2.7.15.tgz cd Python-2.7.15 ./configure --prefi ...

  4. Windows平台的JDK安装及IDEA配置JDK的过程

    1.下载安装包 jdk-8u201-windows-x64.exe,即jdk1.8.0_201 链接:https://pan.baidu.com/s/1WYaTlProtHkC_KyMHIUBxQ?p ...

  5. ATtiny88初体验(二):呼吸灯

    ATtiny88初体验(二):呼吸灯 前面的"点灯"实验实现了间隔点亮/熄灭LED,但是间隔时间和亮度都没法控制,为了解决这个问题,可以使用ATtiny88的定时器模块. ATti ...

  6. [信友队图灵杯中级组-D]基础循环结构练习题

    2023-5-13 题目 题目传送门 难度&重要性(1~10):6.5 题目来源 信友队图灵杯 题目算法 构造 解题思路 我们可以知道,在一开始我们得到的 \(a\) 数组是 \(1,2,3, ...

  7. [远程Call]32位远程多参数带返回调用

    [远程Call]32位远程多参数带返回调用 引子 在Windows上可以使用CreateRemoteThread实现远程Call,但是有不带返回值且只能传递一个参数的限制. 解决思路 将多个参数利用V ...

  8. 入门篇-其之一-第一个Java程序

    ️注意: 本文中包含实际操作,需要安装JDK.如果需要安装JDK,请按照这篇文章的步骤进行安装:点我查看JDK安装教程 小白可以多看几遍这篇文章,多敲几次代码 前面我们已经安装了JDK,接下来就是写一 ...

  9. 【RocketMQ】顺序消息实现总结

    全局有序 在RocketMQ中,如果使消息全局有序,可以为Topic设置一个消息队列,使用一个生产者单线程发送数据,消费者端也使用单线程进行消费,从而保证消息的全局有序,但是这种方式效率低,一般不使用 ...

  10. 聊一聊 TLS/SSL

    哈喽大家好,我是咸鱼 当我们在上网冲浪的时候,会在浏览器界面顶部看到一个小锁标志,或者网址以 "https://" 开头 这意味着我们正在使用 TLS/SSL 协议进行安全通信.虽 ...