本文部分摘自 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. 流本身不存储元素,并且不会改变源对象,相反,它会返回一个持有结果的新流
  2. 流可以在不使用赋值或可变数据的情况下对有状态的系统建模
  3. 流是一种声明式编程风格,它声明想要做什么,而非指明如何做
  4. 流的迭代过称为内部迭代,你看不到迭代过程,可读性更强
  5. 流是懒加载的,它会等到需要时才执行

流创建

创建流的方式有很多,下面逐个介绍:

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 流式编程的更多相关文章

  1. Java8新特性 Stream流式思想(二)

    如何获取Stream流刚开始写博客,有一些不到位的地方,还请各位论坛大佬见谅,谢谢! package cn.com.zq.demo01.Stream.test01.Stream; import org ...

  2. Java8新特性 Stream流式思想(一)

    遍历及过滤集合中的元素使用传统方式遍历及过滤集合中的元素package cn.com.zq.demo01.Stream.test01.Stream; import java.util.ArrayLis ...

  3. Java8新特性 Stream流式思想(三)

    Stream接口中的常用方法 forEach()方法package cn.com.cqucc.demo02.StreamMethods.Test02.StreamMethods; import jav ...

  4. JDK8新特性(二) 流式编程Stream

    流式编程是1.8中的新特性,基于常用的四种函数式接口以及Lambda表达式对集合类数据进行类似流水线一般的操作 流式编程分为大概三个步骤:获取流 → 操作流 → 返回操作结果 流的获取方式 这里先了解 ...

  5. 这可能是史上最好的 Java8 新特性 Stream 流教程

    本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...

  6. Java8 新特性之流式数据处理

    一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现.比如我们希望对一个包含整数的 ...

  7. Java8 新特性之流式数据处理(转)

    转自:https://www.cnblogs.com/shenlanzhizun/p/6027042.html 一. 流式处理简介 在我接触到java8流式处理的时候,我的第一感觉是流式处理让集合操作 ...

  8. Java1.8新特性 - Stream流式算法

    一. 流式处理简介   在我接触到java8流式数据处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现.比如我们希望对一个包 ...

  9. Java8新特性Stream流应用示例

    Java8新特性介绍 过滤集合 List<String> newList = list.stream().filter(item -> item != null).collect(C ...

随机推荐

  1. vue获取下拉框select的值

    1.我写的是循环遍历,然后获取id :value="v.id"这就是获取的id然后打印就可以获取id了

  2. 如何免费安装正版Adobe

    现在正版的Adobe都非常的贵,如果你想不花钱又想下载正版的Adobe,那么就请花几分钟时间学习以下本篇博客,告诉你如何免费下载正版Adobe! [一定要读完,不要看到一半就以为教您下载的是付费版] ...

  3. Fabric1.4.4 多机solo共识搭建

    简单记录一下fabric版本1.4.4的搭建部署,运行环境为CentOs7.8,如有错误欢迎批评指正.然后就是本编文章不对环境搭建做说明,基础环境搭建看这里. 1.总体设计 本案例部署一个排序(ord ...

  4. shell-字符串及整数操作符讲解与多实践

    1. 字符串测试操作符 字符串测试操作符的作用:比较两个字符串是否相同.字符串长度是否为零,字符串是否为null(注:bash区分零长度字符串和空字符串)等. "="比较两个字符串 ...

  5. spring cloud 实现基于Nacos权重的负载均衡

    package com.water.step.service.user.nacos; import com.alibaba.nacos.api.exception.NacosException; im ...

  6. docker-阿里云加速

    系统版本 centos7 阿里云登录 ->容器镜像服务->镜像加速器 复制下面的直接执行即可     sudo mkdir -p /etc/docker sudo tee /etc/doc ...

  7. Nginx禁止html等缓存

    +++ date="2020-10-16" title="Nginx禁止html等缓存" tags=["nginx"] categories ...

  8. centos8平台nginx服务配置打开文件限制max open files limits

    一,nginx启动时提示打开文件数,ulimit的配置不起作用: 1, 2020/04/26 14:27:46 [notice] 1553#1553: getrlimit(RLIMIT_NOFILE) ...

  9. Mosquitto服务器的日志分析

    启动Mosquitto后,我们可以看到Mosquitto的启动日志: 1515307521: mosquitto version 1.4.12 (build date 2017-06-01 13:03 ...

  10. Ubuntu服务安装

    一.ifconfig命令安装 sudo apt install net-tools 二.ssh服务安装 sudo apt-get install openssh-server netstat -ltn ...