Java8 新特性 —— Stream 流式编程
本文部分摘自 On Java 8
流概述
集合优化了对象的存储,大多数情况下,我们将对象存储在集合是为了处理他们。使用流可以帮助我们处理对象,无需迭代集合中的元素,即可直接提取和操作元素,并添加了很多便利的操作,例如查找、过滤、分组、排序等一系列操作。
流的一个核心好处是:它使得程序更加短小并且易于理解,当结合 Lambda 表达式和方法引用时,会让人感觉自成一体。总而言之,流就是一种高效且易于使用的处理数据的方式。
观察下面的例子:
public class Randoms {
public static void main(String[] args) {
new Random(47) // 创建 Random 对象,并给一个种子
.ints(5, 20) // 产生一个限定了边界的随机整数流
.distinct() // 使流中的整数不重复
.limit(7) // 取前7个元素
.sorted() // 排序
.forEach(System.out::println); // 根据传递给它的函数对流中每个对象执行操作
}
}
通过上面的示例,我们可以发现流有如下特点:
- 流本身不存储元素,并且不会改变源对象,相反,它会返回一个持有结果的新流
- 流可以在不使用赋值或可变数据的情况下对有状态的系统建模
- 流是一种声明式编程风格,它声明想要做什么,而非指明如何做
- 流的迭代过称为内部迭代,你看不到迭代过程,可读性更强
- 流是懒加载的,它会等到需要时才执行
流创建
创建流的方式有很多,下面逐个介绍:
1. Stream.of()
通过 Stream.of()
可以很容易地将一组元素转化为流
Stream.of(new Bubble(1), new Bubble(2), new Bubble(3)).forEach(System.out::println);
Stream.of("a", "b", "c", "d", "e", "f").forEach(System.out::print);
Stream.of(3.14159, 2.718, 1.618).forEach(System.out::println);
2. stream()
每个集合也可以通过调用 stream()
方法来产生一个流
List<Bubble> list = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
list.stream().forEach(System.out::print);
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c", "d", "e", "f"));
set.stream().forEach(System.out::print);
3. Stream.generate()
使用 Stream.generate()
搭配 Supplier<T>
生成 T 类型的流
Stream.generate(Math::random).limit(10).forEach(System.out::print);
4. Stream.iterate()
Stream.iterate()
产生的流的第一个元素是种子,然后把种子传递给方法,方法的运行结果被添加到流,并作为下次调用 iterate()
的第一个参数
Stream.iterate(0, n -> n + 1).limit(10).forEach(System.out::print)
使用 Stream.generate()
和 Stream.iterate()
生成的无限流一定要用 limit()
截断
5. Stream.builder()
使用建造者模式创建一个 builder
对象,然后将创建流所需的多个信息传递给它,最后 builder
对象执行创建流的操作
Stream.Builder<String> builder = Stream.builder();
builder.add("a");
builder.add("b");
...
builder.build(); // 创建流
// builder.add("c") // 调用 build() 方法后继续添加元素会产生异常
6. Arrays.stream()
Arrays 类中有一个名为 stream()
的静态方法用于把数组转换成流
Arrays.stream(new double[] {3.14159, 2.718, 1.618}).forEach(System.out::print);
Arrays.stream(new int[] {1, 3, 5}).forEach(System.out::print);
Arrays.stream(new long[] {11, 22, 44, 66}).forEach(System.out::print);
// 选择一个子域
Arrays.stream(new int[] {1, 3, 5, 7, 15, 28, 37}, 3, 6).forEach(System.out::print);
最后一次 stream()
的调用有两个额外的参数,第一个参数告诉 stream()
从数组的哪个位置开始选择元素,第二个参数告知在哪里停止
7. IntStream.range()
IntStream
类提供 range()
方法用于生成整型序列的流,编写循环时,这个方法会更加便利
IntStream.range(10, 20).sum(); // 求得 10 - 20 的序列和
IntStream.range(10, 20).forEach(System.out::print); // 循环输出 10 - 20
8. 随机数流
Random 类被一组生成流的方式增强了,可以生成一组随机数流
Random rand = new Random(47);
// 产生一个随机流
rand.ints().boxed();
// 控制上限和下限
rand.ints(10, 20).boxed();
// 控制流的大小
rand.ints(2).boxed();
// 控制流的大小和界限
rand.ints(3, 3, 9).boxed();
Random 类除了能生成基本类型 int,long,double 的流,使用 boxed()
操作会自动把基本类型包装为对应的装箱类型
9. 正则表达式
Java8 在 java.util.regex.Pattern
中新增了一个方法 splitAsStream()
,这个方法可以根据传入的公式将字符序列转化为流
Pattern.compile("[.,?]+").splitAsStream("a,b,c,d,e").forEach(System.out::print);
中间操作
中间操作具体包括去重、过滤、映射等操作,作用于从流中获取的每一个对象,并返回一个新的流对象。
1. 跟踪和调试
peek()
操作的目的是帮助调试,它允许你无修改地查看流中的元素
Stream.of("a b c d e".split(" ")).map(w -> w + " ").peek(System.out::print);
2. 流元素排序
sorted()
可以帮助我们实现对流元素的排序,如果不使用默认的自然排序,则需要传入一个比较器,也可以把 Lambda 函数作为参数传递给 sorted()
Stream.of("a b c d e".split(" ")).sorted(Comparator.reverseOrder())
.map(w -> w + " ").peek(System.out::print);
3. 移除元素
distinct()
可用于消除流中的重复元素
new Random(47).ints(5, 20).distinct().limit(7).forEach(System.out::println);
filter(Predicate)
将元素传递给过滤函数,若结果为 true,则保留元素
// 检测质数
Stream.iterate(2, n -> n + 1).filter(i -> i % 2 ==0)
.limit(10).forEach(System.out::print)
4. 应用函数到元素
map(Function)
将函数操作应用到输入流的元素,并将返回值传递到输出流
Arrays.stream(new String[] {"12", "23", "34"}).map(s -> "[" + s + "]")
.forEach(System.out::print)
另外还有 mapToInt(ToIntFunction)
、mapToLong(ToLongFunction)
、mapToDouble(ToDoubleFunction)
,操作和 map(Function)
相似,只是结果流为各自对应的基本类型
如果在将函数应用到元素的过程中抛出了异常,此时会把原始元素放到输出流
5. 组合流
使用 flatMap()
将产生流的函数应用在每个元素上,然后将产生每个流都扁平化为元素
Stream.of(1, 2, 3).flatMap(i -> Stream.of("hello" + i)).forEach(System.out::println);
另外还有 flatMapToInt(Function)
、flatMapToLong(Function)
、flatMapToDouble(Function)
,操作和 flatMap()
相似,只是结果元素为各自对应的基本类型
Optional 类
如果在一个空流中尝试获取元素,结果肯定是得到一个异常。我们希望可以得到友好的提示,而不是糊你一脸 NullPointException。Optional 的出现就是为了解决臭名昭著的空指针异常
一些标准流操作返回 Optional 对象,因为它们不能保证预期结果一定存在,包括:
findFirst()
返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty
findAny()
返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty
max()
和min()
返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty
reduce(Function)
将函数的返回值包装在 Optional 中
1. 便利函数
Optional 类本质上是一个容器对象,所谓容器是指:它可以保存类型 T 的值,也可以保存一个 null。此外,Optional 提供了许多有用的方法,可以帮助我们不用显示地进行空值检测:
ifPresent()
是否有值存在,存在放回 true,否则返回 false
ifPresent(Consumer)
当值存在时调用 Consumer,否则什么也不做
orElse(otherObject)
如果值存在则直接返回,否则生成 otherObject
orElseGet(Supplier)
如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象
orElseThrow(Supplier)
如果值存在则直接返回,否则使用 Supplier 函数生成一个异常
下面是对 Optional 的一个简单应用
class OptionalBasics {
static void test(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst()); // 生成一个空流
}
}
2.创建 Optional
当我们需要在自己的代码中加入 Optional 时,可以使用下面三个静态方法:
empty()
生成一个空 Optional
of(value)
将一个非空值包装到 Optional 里
ofNullable(value)
针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中
3. Optional 对象操作
当我们的流管道生成 Optional 对象,下面三个方法可以使得 Optional 能做更多后续操作:
filter(Predicate)
对 Optional 中的内容应用 Predicate 并将结果返回。如果 Optional 不满足 Predicate,将 Optional 转化为空 Optional 。如果 Optional 已经为空,则直接返回空 Optional
map(Function)
如果 Optional 不为空,应用 Function 于 Optional 中的内容,并返回结果,否则直接返回 Optional.empty
flatMap(Function)
一般应用于已生成 Optional 的映射函数,所以
flatMap()
不会像map()
那样将结果封装在 Optional 中
终端操作
终端操作将获取流的最终结果,至此我们无法再继续往后传递流。可以说,终端操作总是我们在使用流时所做的最后一件事
1. 数组
当我们需要得到数组类型的数据以便于后续操作时,可以使用下述方法产生数组:
toArray()
将流转换成适当类型的数组
toArray(generetor)
生成自定义类型的数组
2. 循环
常见的如 forEach(Consumer)
,另外还有 forEachOrdered(Consumer)
,保证按照原始流的顺序操作。第二种形式仅在引入并行流时才有意义。所谓并行流是将流分割为多个,并在不同的处理器上分别执行。由于多处理器并行操作的原因,输出的结果可能会不一样,因此需要用到 forEachOrdered(Consumer)
3. 集合
在这里我们只是简单介绍一下常见的 Collectors 示例,实际上它还有一些非常复杂的实现。大多数情况下,java.util.stream.Collectors
中预设的 Collector 就能满足我们的需求
collect(Collector)
使用 Collector 收集流元素到结果集合中
collect(Supplier, BiConsumer, BiConsumer)
第一个参数创建一个新的结果集合,第二个参数将下一个元素收集到结果集合中,第三个参数用于将两个结果集合合并起来
4. 组合
组合意味着将流中所有元素以某种方式组合为一个元素
reduce(BinaryOperator)
使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional
reduce(identity, BinaryOperator)
功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果
看一段代码示例:
Stream.generate(Math::random).limit(10)
.reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1).ifPresent(System.out::println);
返回的结果是 Optional 类型,Lambda 表达式中的第一个参数 fr0 是 reduce 中上一次调用的结果,而第二个参数 fr1 是从流传递过来的值
5. 匹配
allMatch(Predicate)
如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算
anyMatch(Predicate)
如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 是停止执行计算
noneMatch(Predicate)
如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算
6. 查找
findFirst()
返回第一个流元素的 Optional,如果流为空返回 Optional.empty
findAny(
返回含有任意流元素的 Optional,如果流为空返回 Optional.empty
7. 信息
count()
流中的元素个数
max(Comparator)
根据所传入的 Comparator 所决定的最大元素
min(Comparator)
根据所传入的 Comparator 所决定的最小元素
8. 数字流信息
average()
求取流元素平均值
max()
和 min()
数值流操作无需 Comparator
sum()
对所有流元素进行求和
Java8 新特性 —— Stream 流式编程的更多相关文章
- Java8新特性 Stream流式思想(二)
如何获取Stream流刚开始写博客,有一些不到位的地方,还请各位论坛大佬见谅,谢谢! package cn.com.zq.demo01.Stream.test01.Stream; import org ...
- Java8新特性 Stream流式思想(一)
遍历及过滤集合中的元素使用传统方式遍历及过滤集合中的元素package cn.com.zq.demo01.Stream.test01.Stream; import java.util.ArrayLis ...
- Java8新特性 Stream流式思想(三)
Stream接口中的常用方法 forEach()方法package cn.com.cqucc.demo02.StreamMethods.Test02.StreamMethods; import jav ...
- JDK8新特性(二) 流式编程Stream
流式编程是1.8中的新特性,基于常用的四种函数式接口以及Lambda表达式对集合类数据进行类似流水线一般的操作 流式编程分为大概三个步骤:获取流 → 操作流 → 返回操作结果 流的获取方式 这里先了解 ...
- 这可能是史上最好的 Java8 新特性 Stream 流教程
本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...
- Java8 新特性之流式数据处理
一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现.比如我们希望对一个包含整数的 ...
- Java8 新特性之流式数据处理(转)
转自:https://www.cnblogs.com/shenlanzhizun/p/6027042.html 一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作 ...
- Java1.8新特性 - Stream流式算法
一. 流式处理简介 在我接触到java8流式数据处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现.比如我们希望对一个包 ...
- Java8新特性Stream流应用示例
Java8新特性介绍 过滤集合 List<String> newList = list.stream().filter(item -> item != null).collect(C ...
随机推荐
- 洛谷 P1903 [国家集训队]数颜色 / 维护队列 带修莫队
题目描述 墨墨购买了一套\(N\)支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: \(1\). \(Q\) \(L\) \(R\)代表询问你从第\(L\) ...
- ubuntu19.10如何设置固定ip
$ip a 看见系统中有两块网卡 lo: ...... ens33: ...... #cd /etc/netplan$ls目录下面有文件01-network-manager-all.yaml $sud ...
- 多测师浅谈 学员实现价值就是我们的幸福_高级讲师肖sir
学员实现价值就是我们的幸福 作为一名资深的IT高级讲师,在传统的行业IT薪资基本都是过万,作为一名IT培训教师,培养出在不同领域的测试,并且接触各种各样的产品,目前市场流行的比如银行业务系统,语音类系 ...
- CF1430 E. String Reversal(div 2)
题目链接:http://codeforces.com/contest/1430/problem/E 题意:有一串长度为n(n<=2*10^5)由小写字母组成的字符串,求通过相邻交换得到其反转串( ...
- TCMalloc 内存分配原理简析
一.TCMalloc TCMalloc简介 为啥要介绍 TCMalloc? 因为golang的内存分配算法绝大部分都是来自 TCMalloc,golang只改动了其中的一小部分.所以要理解golang ...
- Python ( 学习基础篇 第二部 )
目录 运算符 算数运算符 比较运算符 赋值运算符 位运算符 逻辑运算符 成员运算符 身份运算符 Python 中运算符的优先级 运算符总结基础语法 判断类型 isinstence 代码块 流程控制 w ...
- DiskLruCache缓存bitmap
public class MainActivity extends AppCompatActivity { private DiskLruCache diskLruCache; ImageView i ...
- spring的xml文件的作用与实现原理
1.Spring读取xml配置文件的原理与实现 https://www.cnblogs.com/wyq178/p/6843502.html 2.首先使用xml配置文件的好处是参数配置项与代码分离,便于 ...
- Java8新特性探索之Optional类
为什么引入Optional类? 身为一名Java程序员,大家可能都有这样的经历:调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法.我们首先要判断这个返回值是否为null,只有在非空的前 ...
- JUC---10JMM
前提:什么是Volatile? Java 虚拟机提供轻量级的同步机制 1.保证可见性------->JMM 2.不保证原子性 3.禁止指令重排 一.什么是JMM 1.JMM : Java内存模型 ...