Java8 新特性 —— Stream 流式编程

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. 流本身不存储元素,并且不会改变源对象,相反,它会返回一个持有结果的新流
  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()

对所有流元素进行求和

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/2600.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • html设置背景图片的代码_html背景代码

    html设置背景图片的代码_html背景代码今天河北魅力网络客户让改一下代码,让背景图片实现不随滚动条来滚动,实现这种效果其实很简单,加个代码就行。<bodybackground=”bj.jpg”style=”background-attachment:fixed”;>style=”background-attachment:fixed”;>这样就实现了背景图片固定效果,虽然简单但很实用…

    2022年9月27日
    0
  • android studio的问题整理

    今天打算尝试使用android stutio,试试效果如何,遇到的问题就在这里整理出来 刚才遇到的:Android studio无法启动,错误信息: “Files in E:\Android\android-studio\system\caches are locked. Android Studio will not be able to start up.”尝试使用管理员权

    2022年3月10日
    61
  • mac安装navicat 激活码【中文破解版】

    (mac安装navicat 激活码)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html1STL5S9V8F-eyJsaWN…

    2022年3月27日
    62
  • dreamweaver cs6 html教程,Dreamweaver cs6安装详细图文教程

    dreamweaver cs6 html教程,Dreamweaver cs6安装详细图文教程类型:Mac应用软件大小:314.6M语言:中文评分:10.0标签:立即下载Dreamweaver这款强大的所见即所得的网页编辑器相信大家都有用过,CS6这个新版本增加了对Html5、css及jqurey的支持,还有其他一些功能的增加。不过建议新手是没必要下这个版本的,毕竟这个版本的功能对于刚接触DW的人来说用处不是很大,用CS5足矣。西西为大家制作了Dreamweavercs6的详细安装图文…

    2022年5月18日
    35
  • webpack es6转es5_nodejs支持es6吗

    webpack es6转es5_nodejs支持es6吗万恶的IE遗臭万年仍然需要填坑ie标准对html/css甚至js的规范简直相差甚远,所以,一般要解决的兼容问题很大一部分是为了解决ie的不兼容,虽然目前流行的ES6语法及规范将IE的考虑抛弃掉,默认放弃对IE的治疗,但是IE的兼容仍然是个问题!即使IE的使用率已经不到1%的市场占比。我们使用着舒服的ES6规范但是为IE又很头疼怎么办呢?Webpack开发了非常厉害的打包转换功能:转ES5!…

    2022年9月25日
    0
  • android游戏开发引擎_android主题引擎

    android游戏开发引擎_android主题引擎随着Android系统的使用越来越广泛,了解一下Android平台下的游戏引擎就非常有必要。而同时因为基于Intelx86的移动设备越来越多,我也非常关注支持x86的移动游戏引擎。然而就目前为止游戏引擎的数量已经非常之多,每个引擎都有不同的特征、价格、成熟度等。通过一些调研之后,我发现有非常多的游戏引擎可用于开发运行在android移动设备端的游戏,其中有些还支持x86系统,另外还有些通过简单的

    2022年10月22日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号